@orkestrel/scaffold 0.0.92 → 0.0.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -251,24 +251,24 @@ The following table lists the core constants.
251
251
 
252
252
  Every error carries a machine-readable `code` and an optional `context` record, and each class has a guard. The following table lists the core errors and their guards.
253
253
 
254
- | API | Kind | Summary |
255
- | --------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
256
- | `BrowserError` | class | Represents the base error for all browser automation operations, carrying the code `BROWSER_ERROR` and a `context` record. |
257
- | `BrowserElementError` | class | Reports a refused element operation with a reason and, for `GONE`, the refresh directive. |
258
- | `isBrowserElementError` | function | Checks whether a value is an element refusal. |
259
- | `BrowserStepError` | class | Reports a journey step whose action did not complete, under the code `BROWSER_STEP_ERROR`, with the performed action in `action`. |
260
- | `isBrowserStepError` | function | Checks whether a value is a journey step error. |
261
- | `CDPError` | class | Reports that a CDP request received an error response from the remote endpoint, under the code `BROWSER_CDP_ERROR`, with the `method`, the CDP `code`, the `message`, and any `data` in its context. |
262
- | `CDPConnectionError` | class | Reports that a CDP request could not be sent or completed because the client was not in a connectable state — not connected, closed while connecting, or the connection dropped mid-request — under the code `BROWSER_CDP_CONNECTION_ERROR`. |
263
- | `CDPTimeoutError` | class | Reports that a pending CDP request was not answered within its timeout window, under the code `BROWSER_CDP_TIMEOUT_ERROR`. |
264
- | `BrowserResultLimitError` | class | Reports that an `evaluate()`/`read()` result exceeded `BROWSER_RESULT_LIMIT` and was rejected in-page before it could overflow the CDP transport frame, under the code `BROWSER_RESULT_LIMIT_ERROR`. |
265
- | `isBrowserError` | function | Narrows an unknown value to a `BrowserError`. |
266
- | `isCDPError` | function | Narrows an unknown value to a `CDPError`. |
267
- | `isCDPConnectionError` | function | Narrows an unknown value to a `CDPConnectionError`. |
268
- | `isCDPTimeoutError` | function | Narrows an unknown value to a `CDPTimeoutError`. |
269
- | `isBrowserResultLimitError` | function | Narrows an unknown value to a `BrowserResultLimitError`. |
270
- | `BrowserConnectionError` | class | Reports that a CDP connection, discovery, or launch attempt failed, under the code `BROWSER_CONNECTION_ERROR`. |
271
- | `isBrowserConnectionError` | function | Narrows an unknown value to a `BrowserConnectionError`. |
254
+ | API | Kind | Summary |
255
+ | --------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
256
+ | `BrowserError` | class | Represents the base error for all browser automation operations, carrying the code `BROWSER_ERROR` and a `context` record. |
257
+ | `BrowserElementError` | class | Reports a refused element operation with a reason and, for `GONE`, the refresh directive. |
258
+ | `isBrowserElementError` | function | Checks whether a value is an element refusal. |
259
+ | `BrowserStepError` | class | Reports a journey step whose action did not complete, under the code `BROWSER_STEP_ERROR`, with the performed action in `action`. |
260
+ | `isBrowserStepError` | function | Checks whether a value is a journey step error. |
261
+ | `CDPError` | class | Reports that a CDP request received an error response from the remote endpoint, under the code `BROWSER_CDP_ERROR`, with the `method`, the CDP `code`, the `message`, and any `data` in its context. |
262
+ | `CDPConnectionError` | class | Reports that a CDP request could not be sent or completed because the client was not connected, closed while connecting, lost its connection, or lost the request's target session, under the code `BROWSER_CDP_CONNECTION_ERROR`. |
263
+ | `CDPTimeoutError` | class | Reports that a pending CDP request was not answered within its timeout window, under the code `BROWSER_CDP_TIMEOUT_ERROR`. |
264
+ | `BrowserResultLimitError` | class | Reports that an `evaluate()`/`read()` result exceeded `BROWSER_RESULT_LIMIT` and was rejected in-page before it could overflow the CDP transport frame, under the code `BROWSER_RESULT_LIMIT_ERROR`. |
265
+ | `isBrowserError` | function | Narrows an unknown value to a `BrowserError`. |
266
+ | `isCDPError` | function | Narrows an unknown value to a `CDPError`. |
267
+ | `isCDPConnectionError` | function | Narrows an unknown value to a `CDPConnectionError`. |
268
+ | `isCDPTimeoutError` | function | Narrows an unknown value to a `CDPTimeoutError`. |
269
+ | `isBrowserResultLimitError` | function | Narrows an unknown value to a `BrowserResultLimitError`. |
270
+ | `BrowserConnectionError` | class | Reports that a CDP connection, discovery, or launch attempt failed, under the code `BROWSER_CONNECTION_ERROR`. |
271
+ | `isBrowserConnectionError` | function | Narrows an unknown value to a `BrowserConnectionError`. |
272
272
 
273
273
  An error names its refusal in `code`. The following table lists every code that reaches a caller, the class that carries it, what throws it, and when.
274
274
 
@@ -276,7 +276,7 @@ An error names its refusal in `code`. The following table lists every code that
276
276
  | ------------------------------ | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
277
277
  | `BROWSER_ERROR` | `BrowserError` | every face | An argument or option is invalid, a page, frame, handle, worker, or manager is closed or disconnected, a protocol payload is off-shape, a navigation fails or passes its timeout, or a refusal names no code of its own. |
278
278
  | `BROWSER_CDP_ERROR` | `CDPError` | `CDPClient.send` and every call that sends through it | The remote endpoint answers a request with an error response. |
279
- | `BROWSER_CDP_CONNECTION_ERROR` | `CDPConnectionError` | `CDPClient.send` and every call that sends through it | The client is not connected, closes while it connects, or loses the connection while a request is pending. |
279
+ | `BROWSER_CDP_CONNECTION_ERROR` | `CDPConnectionError` | `CDPClient.send` and every call that sends through it | The client is not connected, closes while it connects, loses its connection while a request is pending, or receives a detach event for the request's target session. |
280
280
  | `BROWSER_CDP_TIMEOUT_ERROR` | `CDPTimeoutError` | `CDPClient.send` and every call that sends through it | A request is not answered within its timeout. |
281
281
  | `BROWSER_RESULT_LIMIT_ERROR` | `BrowserResultLimitError` | `evaluate()` and `read()` in both placements, `readBrowserCapture`, and `readBrowserSnapshot` | A serialized result or capture is longer than `BROWSER_RESULT_LIMIT` characters, or a DOM snapshot holds more nodes than its limit. |
282
282
  | `BROWSER_CONNECTION_ERROR` | `BrowserConnectionError` | `Browser.connect()`, `WebSocketCDPTransport`, and `SocketCDPTransport`; `fetchCDPTargets` returns it | A connection, discovery, or launch attempt fails, or a transport sends before it opens or after it closes. |
@@ -348,8 +348,7 @@ import {
348
348
  try {
349
349
  await element.click()
350
350
  } catch (error) {
351
- if (isBrowserElementError(error))
352
- log(error.code, error.context) // { reason: 'OCCLUDED', … }
351
+ if (isBrowserElementError(error)) log(error.code, error.context) // { reason: 'OCCLUDED', … }
353
352
  else if (isCDPError(error)) log(error.code, error.context)
354
353
  else if (isCDPConnectionError(error)) log(error.code)
355
354
  else if (isCDPTimeoutError(error)) log(error.code)
@@ -1053,6 +1052,7 @@ The following table lists the core types.
1053
1052
  | `BrowserProxy` | interface | Describes proxy settings used when creating an isolated browser context. |
1054
1053
  | `BrowserDownloadOptions` | interface | Describes the download policy for a browser context. |
1055
1054
  | `BrowserContextOptions` | interface | Describes the options for creating and configuring an isolated browser context. |
1055
+ | `BrowserContextDisposal` | type | Records Chromium's disposal acknowledgement or the failure that left disposal unconfirmed. |
1056
1056
  | `BrowserContextEventMap` | type | Maps the browser-context lifecycle events. |
1057
1057
  | `BrowserJourneyBinding` | type | Binds a native action's string argument to a literal or to one declared parameter by name. |
1058
1058
  | `BrowserJourneyParameter` | interface | Declares one parameter a journey takes: its default, or none when it is a secret. |
@@ -1164,6 +1164,8 @@ A page hands the navigation steps it accepts, typed by `BrowserNavigationEventMa
1164
1164
 
1165
1165
  The Node runtime discovers a browser listening on a CDP port, attaches to it, or launches a Chromium-family process. The following fence probes, connects, and destroys.
1166
1166
 
1167
+ Every library launch, including `browse`, passes `--no-first-run`, `--no-default-browser-check`, `--disable-sync`, and `--disable-features=msImplicitSignin` alongside your launch arguments. On Edge, fresh automation profiles launch with sync off and implicit sign-in disabled, so they carry no Microsoft account, synced extensions, or synced settings. This behavior was measured on Windows with Edge 154.0.4258.53 on 2026-10-05. Sync stays off so account extensions cannot install during page imports and reset the browser's URL loader factories. The launcher merges every caller and library disabled feature into one `--disable-features=` switch, because Chromium reads only the last occurrence. Chrome and Chromium ignore the unknown Edge feature name.
1168
+
1167
1169
  ```ts
1168
1170
  import { createBrowser } from '@orkestrel/browser/server'
1169
1171
 
@@ -1249,6 +1251,8 @@ The following table lists the server constants.
1249
1251
  | `BROWSER_RUN_FILE` | const | Names the persisted run snapshot. |
1250
1252
  | `BROWSER_RUN_DIRECTORY` | const | Names the journey directory holding its runs. |
1251
1253
  | `BROWSER_SERVER_POOL_SIZE` | const | Sets the default number of warm browsers to 1. |
1254
+ | `BROWSER_SERVER_CONTEXTS` | const | Sets the default context capacity per browser, including the shared holder. |
1255
+ | `BROWSER_SERVER_CONTEXTS_LIMIT` | const | Limits context capacity to the largest measured per-browser count. |
1252
1256
  | `BROWSER_SERVER_POOL_LIMIT` | const | Limits the number of warm browsers to 3. |
1253
1257
  | `BROWSER_SERVER_RESTARTS` | const | Permits one failed refill before the next failure spends the bound. |
1254
1258
  | `BROWSER_SERVER_RECORD` | const | Names the browser process record in each profile. |
@@ -1377,31 +1381,35 @@ if (found !== undefined) {
1377
1381
 
1378
1382
  The following table lists the server types.
1379
1383
 
1380
- | API | Kind | Summary |
1381
- | ------------------------------ | --------- | --------------------------------------------------------------------------------------------------------------------- |
1382
- | `BrowserEngine` | type | Names a supported browser engine (raw CDP targets Chromium-family browsers only). |
1383
- | `BrowserConnection` | type | Names how the browser connection was established. |
1384
- | `BrowserStatus` | type | Names the lifecycle status of a browser wrapper. |
1385
- | `BrowserDiscoveryResult` | interface | Describes the result of passive browser discovery. |
1386
- | `SystemBrowserOptions` | interface | Describes the options overriding `findSystemBrowsers`'/`findSystemBrowser`'s candidate sources. |
1387
- | `SystemBrowser` | type | Represents one discovered browser executable on this machine. |
1388
- | `BrowserProfileRecord` | interface | Names the browser a browse profile serves, which a later start's sweep reads. |
1389
- | `BrowserProfileResult` | interface | Describes the resolved browser profile directory used for a Chromium-family launch. |
1390
- | `BrowserCDPOptions` | interface | Configures the CDP (Chrome DevTools Protocol) connection. |
1391
- | `BrowserEventMap` | type | Maps the events a `BrowserInterface` emits. |
1392
- | `BrowserOptions` | interface | Describes the options for creating a `Browser` instance. |
1393
- | `BrowserInterface` | interface | Wraps a browser with discovery, connection management, and lifecycle control. |
1394
- | `WebSocketCDPTransportOptions` | interface | Describes the options for creating a `WebSocketCDPTransport` instance. |
1395
- | `FileBrowserStoreOptions` | interface | Configures a file store: its root under the checkout and the listing cap. |
1396
- | `BrowserLaunchFunction` | type | Creates a browser a browse server connects while warming its pool. |
1397
- | `BrowserSlot` | interface | Holds one warm browser a browse server owns: the browser, its profile, its isolated context, and its started toolset. |
1398
- | `BrowserSlotWatch` | interface | Holds the listeners and resolver of one browser slot's loss watch. |
1399
- | `BrowserMCPServerOptions` | interface | Configures the browse server. |
1400
- | `BrowserServerToolName` | type | Names the server tools that manage independent browser holders. |
1401
- | `BrowserServerHolder` | interface | Identifies one admitted browser lifetime independently of its current pool token. |
1402
- | `BrowserServerCatalog` | interface | Describes the JSON catalog returned by the holder tools. |
1403
- | `BrowserServerMirror` | interface | Binds shared catalog subscriptions to the exact token that published them. |
1404
- | `BrowserMCPServerInterface` | interface | Serves the browser vocabulary and the journey tools over MCP on stdio. |
1384
+ | API | Kind | Summary |
1385
+ | ------------------------------ | --------- | ----------------------------------------------------------------------------------------------- |
1386
+ | `BrowserEngine` | type | Names a supported browser engine (raw CDP targets Chromium-family browsers only). |
1387
+ | `BrowserConnection` | type | Names how the browser connection was established. |
1388
+ | `BrowserStatus` | type | Names the lifecycle status of a browser wrapper. |
1389
+ | `BrowserDiscoveryResult` | interface | Describes the result of passive browser discovery. |
1390
+ | `SystemBrowserOptions` | interface | Describes the options overriding `findSystemBrowsers`'/`findSystemBrowser`'s candidate sources. |
1391
+ | `SystemBrowser` | type | Represents one discovered browser executable on this machine. |
1392
+ | `BrowserProfileRecord` | interface | Names the browser a browse profile serves, which a later start's sweep reads. |
1393
+ | `BrowserProfileResult` | interface | Describes the resolved browser profile directory used for a Chromium-family launch. |
1394
+ | `BrowserCDPOptions` | interface | Configures the CDP (Chrome DevTools Protocol) connection. |
1395
+ | `BrowserEventMap` | type | Maps the events a `BrowserInterface` emits. |
1396
+ | `BrowserOptions` | interface | Describes the options for creating a `Browser` instance. |
1397
+ | `BrowserInterface` | interface | Wraps a browser with discovery, connection management, and lifecycle control. |
1398
+ | `WebSocketCDPTransportOptions` | interface | Describes the options for creating a `WebSocketCDPTransport` instance. |
1399
+ | `FileBrowserStoreOptions` | interface | Configures a file store: its root under the checkout and the listing cap. |
1400
+ | `BrowserLaunchFunction` | type | Creates a browser a browse server connects while warming its pool. |
1401
+ | `BrowserSlot` | interface | Holds one pooled browser and its exclusively owned profile. |
1402
+ | `BrowserSlotWatch` | interface | Holds the listeners and resolver of one browser slot's loss watch. |
1403
+ | `BrowserServerContext` | interface | Holds one prepared or assigned context generation and its downloads directory. |
1404
+ | `BrowserServerLease` | interface | Binds a holder generation to the exact browser lease reserved for its lifetime. |
1405
+ | `BrowserServerLoss` | interface | Records a lost generation's cause and its own last URL. |
1406
+ | `BrowserServerWatch` | interface | Holds page subscriptions for one prepared or assigned context generation. |
1407
+ | `BrowserMCPServerOptions` | interface | Configures the browse server. |
1408
+ | `BrowserServerToolName` | type | Names the server tools that manage independent browser holders. |
1409
+ | `BrowserServerHolder` | interface | Identifies one admitted browser lifetime independently of its current pool token. |
1410
+ | `BrowserServerCatalog` | interface | Describes the JSON catalog returned by the holder tools. |
1411
+ | `BrowserServerMirror` | interface | Binds shared catalog subscriptions to the exact generation that published them. |
1412
+ | `BrowserMCPServerInterface` | interface | Serves the browser vocabulary and the journey tools over MCP on stdio. |
1405
1413
 
1406
1414
  ### Browser
1407
1415
 
@@ -1670,7 +1678,7 @@ await context?.destroy() // local detach
1670
1678
 
1671
1679
  Operations shared by a top-level page and an iframe document. Every asynchronous member takes a trailing `BrowserCallOptions` carrying `timeout` and `signal`. `evaluate` runs in the page's main world; `read` and the element managers run in one isolated world per document, which the page drops on `Runtime.executionContextDestroyed` and `Runtime.executionContextsCleared` and creates again on the next call. A child frame follows its attached out-of-process session when Chromium splits the frame into another target.
1672
1680
 
1673
- A child target runs only after the page has instrumented it. A context configures each page session's auto-attach with `waitForDebuggerOnStart: true`, and the page does the same on each out-of-process frame session, with an `iframe` filter, and on each popup it opens, so every target the page's auto-attach reaches attaches paused. For a frame session the page sends `Page.enable`, `Runtime.enable`, `Page.setLifecycleEventsEnabled`, and `Target.setAutoAttach` and then resumes the target with `Runtime.runIfWaitingForDebugger`; it resumes a worker after `Runtime.enable`, a popup after the popup's domains and network manager start, and any other target at once. The first commit and load of a frame that moved into another process therefore arrive on its new session and carry the loader of the navigation that moved it: on Chromium 141, the new session reports `Page.frameNavigated` with that loader after the resume, the page session then reports the swap, and the new session reports `load` with the same loader. A target the page cannot enable is resumed and then detached, because on Chromium 141 a paused target stays paused after a detach without a resume, so it does not run and its parent document does not reach `load`; the detach waits until the resume attempt settles through a reply, a refusal, the client's timeout, or the connection's close. The page reads a frame session's frame tree only when the attach named no `parentFrameId`, and then only for the frame's parent, URL, and name. [`tests/src/core/BrowserPage.test.ts`](../tests/src/core/BrowserPage.test.ts) pins the order of each step.
1681
+ A page instruments frame sessions, workers, and popups before or while resuming them, resumes any other target at once, and releases a non-frame target reported by a frame session without instrumenting it. A context configures each page session's auto-attach with `waitForDebuggerOnStart: true`, and the page does the same on each out-of-process frame session, with an `iframe` filter, and on each popup it opens, so every target the page's auto-attach reaches attaches paused. For a frame session the page sends `Page.enable`, `Runtime.enable`, `Page.setLifecycleEventsEnabled`, and `Target.setAutoAttach` and then resumes the target with `Runtime.runIfWaitingForDebugger`. The first commit and load of a frame that moved into another process therefore arrive on its new session and carry the loader of the navigation that moved it: on Chromium 141, the new session reports `Page.frameNavigated` with that loader after the resume, the page session then reports the swap, and the new session reports `load` with the same loader. The page resumes an attached dedicated or shared worker after `Runtime.enable` replies and a popup after the popup's domains and network manager start. It resumes a service worker after queuing `Runtime.enable`; on Edge 154.0.4258.53, that reply waits for worker startup. On Edge 154.0.4258.53, a page's dedicated and shared workers start and answer messages under this setup; the dedicated worker attaches paused, while the shared worker runs without an attachment reported by the page session. The page emits `worker` only after `Runtime.enable` replies. A non-frame target reported by a frame session is resumed and detached through that reporting session without a `worker` event. A target the page cannot enable is resumed and then detached, because on Chromium 141 a paused target stays paused after a detach without a resume, so it does not run and its parent document does not reach `load`; the detach waits until the resume attempt settles through a reply, a refusal, the client's timeout, or the connection's close. The page reads a frame session's frame tree only when the attach named no `parentFrameId`, and then only for the frame's parent, URL, and name. [`tests/src/core/BrowserPage.test.ts`](../tests/src/core/BrowserPage.test.ts) pins the order of each step, and [`tests/service/browser.test.ts`](../tests/service/browser.test.ts) proves worker startup in Chromium.
1674
1682
 
1675
1683
  | Method | Returns | Summary |
1676
1684
  | ------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -2341,18 +2349,18 @@ await browser.destroy() // terminates and awaits the owned process
2341
2349
 
2342
2350
  #### `BrowserMCPServerInterface`
2343
2351
 
2344
- Serves the browser vocabulary and the journey tools over MCP on stdio. The server warms its pool at start and holds one validated lease for the shared browser and one for each holder `acquire` admits. Initialization and tool calls await setup; a lost lease is replaced within the pool's restart bound. Each launch creates an exclusive `ROOT/.profiles/PID-UUID/` profile and an isolated context. `tools/list` answers while warming. Adopted page tools are mirrored until withdrawn or their lease is lost.
2352
+ Serves the browser vocabulary and the journey tools over MCP on stdio. The server warms its pool at start and places the shared holder and each holder `acquire` admits in its own isolated context on a validated pooled browser. Initialization and tool calls await setup; a lost browser is replaced within the pool's restart bound. Each launch creates an exclusive `ROOT/.profiles/PID-UUID/` profile and one prepared context. `tools/list` answers while warming. Adopted page tools are mirrored until withdrawn or the shared holder loses its browser.
2345
2353
 
2346
- A disconnect or a crash of the current page retires its slot; a background-page crash leaves the lease intact. Hand-out, per-call, and post-failure pings detect browsers that stopped answering. An interrupted failure answers `BROWSER_SERVER_UNRESOLVED`, a known success remains successful, and pending loss notes prefix the next string outcome on a successor or a refusal. Every pending loss is reported. Calls are never repeated, and replacement pages start at `about:blank`.
2354
+ A disconnect retires the browser and every holder context on it; a crash of a holder's current page replaces only that holder's context, and a background-page crash leaves the holder attached. Hand-out, per-call, and post-failure pings detect browsers that stopped answering. An interrupted failure answers `BROWSER_SERVER_UNRESOLVED`, a known success remains successful, and pending loss notes prefix the next string outcome on a successor or a refusal. Every pending loss is reported. Calls are never repeated, and replacement pages start at `about:blank`.
2347
2355
 
2348
2356
  The end of input, `SIGINT`, and `SIGTERM` stop admission and destroy the pool. Teardown releases watch listeners, destroys each toolset before its browser, and rechecks recorded folders. Unconfirmed termination retains its slot; a stranded launch prevents further launches. A timed-out ping forces termination before teardown. On Linux, a root process launches with `--no-sandbox`; other launches keep the sandbox.
2349
2357
 
2350
- | Method | Returns | Summary |
2351
- | --------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
2352
- | `start` | `Promise<void>` | Serves stdio, sweeps the profiles ended servers left, warms the pool, and resolves after the shared holder leases a warm browser; the legacy handshake and every tool call await the same setup. Rejects with `BROWSER_SERVER_UNAVAILABLE` when no browser can serve and with `BROWSER_TOOLSET_ENDED` after `destroy()`, and resolves when `destroy()` interrupts setup. |
2353
- | `destroy` | `Promise<void>` | Stops admission, tears down every browser, its toolset, and its profile, then rechecks the folders it answers for. |
2358
+ | Method | Returns | Summary |
2359
+ | --------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
2360
+ | `start` | `Promise<void>` | Serves stdio, sweeps the profiles ended servers left, warms the pool, and resolves after the shared holder consumes a prepared context on a warm browser; the legacy handshake and every tool call await the same setup. Rejects with `BROWSER_SERVER_UNAVAILABLE` when no browser can serve and with `BROWSER_TOOLSET_ENDED` after `destroy()`, and resolves when `destroy()` interrupts setup. |
2361
+ | `destroy` | `Promise<void>` | Stops admission, tears down every browser, its toolset, and its profile, then rechecks the folders it answers for. |
2354
2362
 
2355
- The viewport option applies to every warmed browser, including spares and refills, and to every page and popup in its isolated context.
2363
+ The viewport option applies to every warmed browser, refills included, and to every page and popup in each of its isolated contexts.
2356
2364
 
2357
2365
  | Option | Type | Default | Behavior |
2358
2366
  | ---------- | ----------------- | ---------------------------- | -------------------------------------------------------------------------------------------- |
@@ -3591,20 +3599,21 @@ The `browse` binary serves the vocabulary and the journey tools over MCP on stdi
3591
3599
  - `BROWSE_HEADLESS`: `true`, `false`, `1`, or `0`. Default: `true`.
3592
3600
  - `BROWSE_EXECUTABLE`: the path of the Chromium executable. Default: the browser `findSystemBrowser` finds.
3593
3601
  - `BROWSE_READONLY`: `true`, `false`, `1`, or `0`; `true` refuses `record`, `save`, `edit`, and `forget`, and `replay` still writes runs. Default: `false`.
3594
- - `BROWSE_POOL`: an integer from `1` through `3` setting the number of browsers the server keeps, and so the most holders at the same time, the shared browser included; see [Run work in parallel](#run-work-in-parallel). Default: `1`.
3595
- - `BROWSE_VIEWPORT`: positive integer dimensions in `WIDTHxHEIGHT` form, such as `390x844`. Applies to every page and popup, including those in spares and refills. Default: the browser's launch default.
3602
+ - `BROWSE_POOL`: an integer from `1` through `3` setting the number of browsers the server keeps, with holder admission bounded separately by `BROWSE_CONTEXTS`; see [Run work in parallel](#run-work-in-parallel). Default: `1`.
3603
+ - `BROWSE_CONTEXTS`: an integer from `1` through `4` setting the number of contexts each browser holds, the shared holder's included; see [Run work in parallel](#run-work-in-parallel). Default: `2`.
3604
+ - `BROWSE_VIEWPORT`: positive integer dimensions in `WIDTHxHEIGHT` form, such as `390x844`. Applies to every page and popup on every browser, refills included. Default: the browser's launch default.
3596
3605
 
3597
- A malformed value ends the process with exit code 1 and one line on standard error. Any other value of `BROWSE_HEADLESS` or `BROWSE_READONLY`, a `BROWSE_POOL` that is not an integer, and a malformed `BROWSE_VIEWPORT` write a `BROWSER_SERVER_ENVIRONMENT` line, such as `browse: BROWSER_SERVER_ENVIRONMENT: BROWSE_HEADLESS must be true, false, 1, or 0, not "sometimes"`. Viewport dimensions must be positive safe integers written as decimal digits with a lowercase `x` separator and no whitespace. An integer `BROWSE_POOL` outside `1` through `3` writes `browse: BROWSER_SERVER_OPTIONS: pool.size must be an integer from 1 through 3`. On Linux a server running as root launches Chromium with `--no-sandbox`, because Chromium refuses to start as root with its sandbox on.
3606
+ A malformed value ends the process with exit code 1 and one line on standard error. Any other value of `BROWSE_HEADLESS` or `BROWSE_READONLY`, a `BROWSE_POOL` or `BROWSE_CONTEXTS` that is not an integer, and a malformed `BROWSE_VIEWPORT` write a `BROWSER_SERVER_ENVIRONMENT` line, such as `browse: BROWSER_SERVER_ENVIRONMENT: BROWSE_HEADLESS must be true, false, 1, or 0, not "sometimes"`. Viewport dimensions must be positive safe integers written as decimal digits with a lowercase `x` separator and no whitespace. An integer `BROWSE_POOL` outside `1` through `3` writes `browse: BROWSER_SERVER_OPTIONS: pool.size must be an integer from 1 through 3`, and an integer `BROWSE_CONTEXTS` outside `1` through `4` writes `browse: BROWSER_SERVER_OPTIONS: pool.contexts must be an integer from 1 through 4`. On Linux a server running as root launches Chromium with `--no-sandbox`, because Chromium refuses to start as root with its sandbox on.
3598
3607
 
3599
- Chromium starts when the server starts, before the client sends a request. The server launches its first browser, checks that it answers a CDP ping, and leases it as the shared browser, which the named tools drive. `initialize` and every tool call wait for that lease, and `ping` and `tools/list` answer while the browser warms. With `BROWSE_POOL` at `2` or `3`, the other browsers warm after the first, and `initialize` does not wait for them. A launch that fails is retried one time. The end of input or `SIGTERM` closes every browser and removes its profile, the process exits with code 0, and a run without a fault writes nothing to standard error.
3608
+ Chromium starts when the server starts, before the client sends a request. The server launches its first browser, checks that it answers a CDP ping, and places the shared holder, which the named tools drive, in that browser's prepared context. `initialize` and every tool call wait for that placement, and `ping` and `tools/list` answer while the browser warms. With `BROWSE_POOL` at `2` or `3`, the other browsers warm after the first, and `initialize` does not wait for them. A launch that fails is retried one time. The end of input or `SIGTERM` closes every browser and removes its profile, the process exits with code 0, and a run without a fault writes nothing to standard error.
3600
3609
 
3601
3610
  A client gives a server a limited time to answer `initialize`. On 2026-10-03, Claude Code's documentation gave 30 s and connected servers in the background, the Codex configuration reference gave 10 s through `startup_timeout_sec`, and Cursor's documentation gave no limit; see [Claude Code's MCP documentation](https://code.claude.com/docs/en/mcp), [the Codex configuration reference](https://learn.chatgpt.com/docs/config-file/config-reference), and [Cursor's MCP documentation](https://cursor.com/docs/context/mcp). On Windows 11 with Edge 154 on 2026-10-04, while a browser test suite ran beside it, the built binary answered `initialize` at most 2.07 s after its spawn at every `BROWSE_POOL` size, a leftover browser to sweep included. No client needs a longer startup timeout.
3602
3611
 
3603
- Set `BROWSE_POOL` to `2` or `3` to keep warm browsers beyond the shared one. A browser no holder leases is a spare: it waits idle until `acquire` admits a holder on it or a holder's browser is lost. After a loss, the holder's next call runs on a spare while a replacement warms, and concurrent calls on that holder share its one replacement. At `1`, the next call after a loss waits for a replacement launch. In the 2026-10-04 run, the next successful call after a kill of the session's browser took a median 1.92 s at `1` and 0.40 s at `2`. On Windows 11 with Edge 154 on 2026-10-04, an idle spare's process tree used about 130% of one core in the first 20 s, then a median near 10%, and its summed working set grew from 1.22 GB to 1.78 GB over 2 minutes, with shared pages counted in every process that maps them. With `BROWSE_HEADLESS=false`, each spare opens its own window.
3612
+ Set `BROWSE_POOL` to `2` or `3` to keep more than one browser. Each browser warms with one prepared context, and `acquire` places a holder on the live browser with the fewest occupied contexts, so a browser that serves no holder takes the next one. After a browser loss, each holder that was on it recovers at its next call: in a free context on another browser, or after the replacement launch when no browser has a free context. Concurrent calls on one holder share its one recovery. At `1`, every recovery after a browser loss waits for the replacement launch. In the contexts measurement on Windows 11 with Edge 154.0.4258.53 on 2026-10-05, acquiring a context took 113 to 266 ms and relaunching a browser 980 to 1898 ms.
3604
3613
 
3605
- When a holder's browser is lost, the shared browser's included, browse never repeats a call. A loss is the browser process exiting, its CDP connection dropping, the renderer of its current page crashing, or a CDP ping it fails before a call or after a failed one; a crashed background tab is not a loss. The following list gives what each call answers around a loss:
3614
+ When a holder loses its browser or its context, the shared holder's included, browse never repeats a call. A loss is the browser process exiting, its CDP connection dropping, or a CDP ping it fails before a call or after a failed one; a current-page renderer crash replaces only its holder's context, and a crashed background tab leaves the holder attached. The following list gives what each call answers around a loss:
3606
3615
 
3607
- - A call after the loss runs on a fresh browser that starts at `about:blank`, and its text opens with a `BROWSER_SERVER_CRASH:` note that names the cause and the lost page's URL. An element reference from the lost page is refused.
3616
+ - A call after the loss runs in a fresh context that starts at `about:blank`, and its text opens with a `BROWSER_SERVER_CRASH:` note that names the cause and the lost page's URL. An element reference from the lost page is refused.
3608
3617
  - A call the loss interrupts answers `BROWSER_SERVER_UNRESOLVED:`: its outcome is unknown, and browse did not repeat it. Read the page before you repeat the call.
3609
3618
  - A call that completed before the loss keeps its result, and the next call carries the note.
3610
3619
  - A call that fails for its own reason on a live browser answers its plain failure, with no note.
@@ -3626,7 +3635,7 @@ When no browser starts at setup, the server refuses on each surface and keeps an
3626
3635
  The server writes each diagnostic as one line on standard error in the form `browse: CODE: DETAIL`, where `CODE` is one of the following codes and `DETAIL` is the cause:
3627
3636
 
3628
3637
  - `BROWSER_SERVER_LAUNCH`: a browser failed to start, and the pool retries it within its bound.
3629
- - `BROWSER_SERVER_EXHAUSTED`: setup's warming or a call's acquire rejected with the pool's `create` error after the launch bound was spent; a spare that spends the bound after setup writes `BROWSER_SERVER_LAUNCH` lines without this line.
3638
+ - `BROWSER_SERVER_EXHAUSTED`: setup's warming or a call's acquire rejected with the pool's `create` error after the launch bound was spent; a browser warming after setup that spends the bound writes `BROWSER_SERVER_LAUNCH` lines without this line.
3630
3639
  - `BROWSER_SERVER_UNAVAILABLE`: setup was refused, and the binary writes this line before it exits with code 1.
3631
3640
  - `BROWSER_SERVER_TEARDOWN`: a browser's termination is unconfirmed or its teardown failed; its profile folder stays until the shutdown recheck, and a shutdown that meets it exits with code 1.
3632
3641
  - `BROWSER_SERVER_SWEEP`: the sweep could not read the `.profiles` folder under `BROWSE_ROOT`, and the server serves without it.
@@ -3689,14 +3698,14 @@ On 2026-10-04, Claude Code 2.1.285 under `claude -p --mcp-config FILE --strict-m
3689
3698
 
3690
3699
  ### Run work in parallel
3691
3700
 
3692
- Acquire a holder when work needs a browser of its own beside the shared one, such as a subagent that checks a page while you drive another through the named tools. Each holder runs on its own pooled browser, with its own profile, isolated context, pages, cookies, storage, and downloads, so its calls run beside the shared browser's calls and beside every other holder's. The named tools, such as `navigate` and `look`, keep serving the shared browser unchanged.
3701
+ Acquire a holder when work needs a context of its own beside the shared one, such as a subagent that checks a page while you drive another through the named tools. Each holder runs in its own isolated context on a pooled browser, with its own pages and downloads directory, so its calls run beside the shared holder's calls and beside every other holder's. The named tools, such as `navigate` and `look`, keep serving the shared holder unchanged.
3693
3702
 
3694
3703
  The following list gives the holder tools that `BROWSER_SERVER_COPY` defines:
3695
3704
 
3696
3705
  - `acquire { purpose }`: admits a holder and answers JSON with the server-minted `holder` id and its browser's `tools` catalog. The `purpose` text describes the work and names the holder in a capacity refusal. Hand the id to the agent that does the work.
3697
3706
  - `execute { holder, name, arguments }`: runs the tool `name` from the holder's catalog with that tool's own arguments inside `arguments`, and answers what the tool answers.
3698
3707
  - `tools { holder }`: lists the holder's catalog, including the page tools its current page registers.
3699
- - `destroy { holder }`: ends the holder and destroys its browser. A call still running on that holder answers `BROWSER_SERVER_UNRESOLVED:`, and a repeated `destroy` of the same holder succeeds again.
3708
+ - `destroy { holder }`: ends the holder and closes its context. A call still running on that holder answers `BROWSER_SERVER_UNRESOLVED:`, and a repeated `destroy` of the same holder succeeds again.
3700
3709
 
3701
3710
  The following fence shows one holder from `acquire` to `destroy`, one request and the text of its answer per line, with the view after the receipt left out; `HOLDER_ID` stands for the id that `acquire` returns.
3702
3711
 
@@ -3711,17 +3720,32 @@ The following fence shows one holder from `acquire` to `destroy`, one request an
3711
3720
  // <- Holder destroyed.
3712
3721
  ```
3713
3722
 
3714
- `destroy` ends the holder's browser process and removes its profile folder, so the holder's pages, cookies, storage, and downloads are gone, and the next holder starts on a fresh browser with a fresh profile. Each browser saves downloads in its own profile, under `ROOT/.profiles/PID-UUID/downloads/`, so copy a file out before you destroy its holder; the end of the server removes the shared browser's downloads the same way. The `contains downloads and destroys their process and profile before capacity is reused cleanly` case of [`tests/service/browse.test.ts`](../tests/service/browse.test.ts) asserts the download path, the exited process, the removed profile, and a clean replacement against a real Chromium.
3723
+ `destroy` closes the holder's context and removes its downloads directory, under `ROOT/.profiles/PID-UUID/contexts/GENERATION-UUID/downloads/`. Copy a file out before destroying its holder. The end of the server closes the shared holder's context and removes its downloads too.
3724
+
3725
+ A holder that `acquire` admits after a `destroy` starts on a clean slate, which is narrower than a fresh browser. Its context starts with no cookies, local or session storage, IndexedDB data, Cache Storage, service worker registration, HTTP cache entry, or permission override; with its own tabs, element references, and catalog; and with an empty downloads directory. The browser process stays and keeps the memory it holds. In the contexts measurement on Windows 11 with Edge 154.0.4258.53 on 2026-10-05, a browser's private memory rose from about 505 MiB to about 670 MiB over its first 5 context generations and then held through 30; a longer run might show more.
3726
+
3727
+ `BROWSE_POOL`, or `pool.size` for `BrowserMCPServer`, counts browsers. `BROWSE_CONTEXTS`, or `pool.contexts`, bounds contexts per browser. Admission is their product, with the shared holder counted; the defaults admit one named holder beside the shared holder. With `contexts: 1`, a size of `3` admits two named holders. At capacity, `acquire` answers immediately with `BROWSER_SERVER_BUSY:` and launches nothing; its text lists each holder by id and purpose, the shared holder as `shared (Shared browser)`, and tells the agent to destroy a holder it no longer needs or to call the named tools to share the shared browser. A holder that `destroy` is ending counts until its context disposal settles. An unknown or ended id answers `BROWSER_SERVER_HOLDER:` and never falls back to the shared holder.
3728
+
3729
+ A failure costs a context only to the holders it reaches, and each of them keeps its id. Each gets a fresh context at its own next call, which answers as the loss list in [Register the browse binary with Claude Code](#register-the-browse-binary-with-claude-code) gives. The following list gives the scope of each failure:
3730
+
3731
+ - A browser loss costs every holder on that browser its context, the shared holder's included. Each receives its own `BROWSER_SERVER_CRASH:` note on its next call, naming the URL its own page last showed.
3732
+ - Holders on another browser receive no note and keep their pages and web state.
3733
+ - A crash of a holder's current page costs only that holder's context. The browser and the other holders on it stay attached.
3734
+ - A call that either failure interrupts answers `BROWSER_SERVER_UNRESOLVED:`, and browse never repeats it.
3715
3735
 
3716
- `BROWSE_POOL`, or `pool.size` for `BrowserMCPServer`, counts every browser, the shared browser included, so a size of `3` admits two holders. At capacity, `acquire` answers immediately with `BROWSER_SERVER_BUSY:` and launches nothing; its text lists each holder by id and purpose, the shared browser as `shared (Shared browser)`, and tells the agent to destroy a holder it no longer needs or to call the named tools to share the shared browser. A holder that `destroy` is ending counts until its browser is gone. An unknown or ended id answers `BROWSER_SERVER_HOLDER:` and never falls back to the shared browser.
3736
+ Keep the default single browser. Set `BROWSE_POOL` to `2` only when a browser loss must leave some holders running: the holders on the surviving browser keep their pages, at the cost of a second idle browser. In the contexts measurement on Windows 11 with Edge 154.0.4258.53 on 2026-10-05, with one holder, a second browser added about 1 GiB of private memory, 2551 MiB against 1414 MiB, and two holders used 917 MiB as contexts on one browser against 1610 MiB on two browsers.
3717
3737
 
3718
- A holder whose browser is lost waits for a spare or a replacement launch, and the `BROWSER_SERVER_CRASH:` note reaches that holder's next call only. Replays of one journey run together across holders, each writing its own run. While a replay of a journey persists its run on any holder, `forget` of that journey answers `BROWSER_JOURNEY_LOCKED: Journey NAME is locked; call forget again.`, where `NAME` is the journey's name, and a replay that starts while `forget` runs answers the same refusal for `replay`.
3738
+ Replays of one journey run together across holders, each writing its own run. While a replay of a journey persists its run on any holder, `forget` of that journey answers `BROWSER_JOURNEY_LOCKED: Journey NAME is locked; call forget again.`, where `NAME` is the journey's name, and a replay that starts while `forget` runs answers the same refusal for `replay`.
3719
3739
 
3720
3740
  The following list gives the limits of holders:
3721
3741
 
3722
- - The default `BROWSE_POOL` of `1` keeps only the shared browser, so `acquire` always answers `BROWSER_SERVER_BUSY:` and the named tools always work. [ROADMAP item 14](../ROADMAP.md) holds the contention measurement that rules the default.
3723
- - `tools/list` mirrors only the shared browser's page tools. A holder's page tools appear only in the catalog that `acquire` and `tools` return, and run through `execute`.
3724
- - A holder keeps its browser until `destroy` or the end of the server, with no idle timeout. An abandoned holder costs one warm browser and one place of capacity, and the `BROWSER_SERVER_BUSY:` text names it.
3742
+ - The confirmed defaults, `BROWSE_POOL` of `1` and `BROWSE_CONTEXTS` of `2`, admit one named holder beside the shared holder. The user confirmed them on 2026-10-05 from M1 (2026-10-05, library-level under a core-test load: 21.85 s at one context against 12.91 s at two) and M2 (2026-10-05, through the built binary on a quiet host: 49.73 s at one context against 29.72 s at two, with disjoint ranges). M2 did not establish a gain from two contexts to three.
3743
+ - Contexts isolate automation, not hostile tenants. Every holder on a browser runs in that browser's process, so drive pages that must not share a process from separate `browse` servers.
3744
+ - Browser relaunches have no bound while a holder uses the browser. A browser lost under a holder spends nothing from the launch bound, and the next grant of a browser restores the bound, so a browser that crashes under every holder's use relaunches at every call that meets the crash. At the default `BROWSE_POOL` of `1` the shared holder always uses the only browser, so a browser that fails every context creation or disposal relaunches the same way.
3745
+ - A renderer that hangs without crashing raises no loss. While its browser answers the CDP ping, a call that fails on the hung page answers its plain failure with no note and no launch, and the holder keeps that context until `destroy` or a later crash of its page.
3746
+ - With `BROWSE_HEADLESS=false`, a popup hides the page that opened it, and the hidden page's animation frames stop, so a click on that page can time out. In the contexts measurement on Windows 11 with Edge 154.0.4258.53 on 2026-10-05, headed clicks on a page behind a popup succeeded 3 of 9 times, and headless clicks 9 of 9. The visibility of every busy holder's page is proven in headless mode only.
3747
+ - `tools/list` mirrors only the shared holder's page tools. A holder's page tools appear only in the catalog that `acquire` and `tools` return, and run through `execute`.
3748
+ - A holder keeps its context until `destroy` or the end of the server, with no idle timeout. An abandoned holder costs one context and one place of capacity, and the `BROWSER_SERVER_BUSY:` text names it.
3725
3749
  - Journey admission covers the holders of one server. A second `browse` process on the same root can still race a replay against `forget`.
3726
3750
  - Codex asks approval for `execute` and `destroy`, because neither is advertised read-only, so under an approval policy that asks, every call a holder makes through `execute` prompts. `acquire` carries no read-only annotation either, and `tools` is advertised read-only.
3727
3751
 
@@ -5374,11 +5374,11 @@ the exact statement:
5374
5374
  1. **doc ↔ source bijection.** Every `function` / `class` / `const` /
5375
5375
  `interface` / `type` row in the `## Surface` tables (the core dispatch
5376
5376
  tables and the `### HTTP transport` + `### WebSocket transport` + `### stdio
5377
- transport` tables) is a real export of the mcp layer (`src/core` or
5377
+ transport` tables) is a real export of the mcp layer (`src/core` or
5378
5378
  `src/server`), and every export of either appears as a Surface row —
5379
5379
  exhaustive, both directions.
5380
5380
  2. **JSON-RPC 2.0 envelope.** A `dispatch` response is always `{ jsonrpc:
5381
- '2.0', … }` with exactly one of `result` / `error`. The success arm always
5381
+ '2.0', … }` with exactly one of `result` / `error`. The success arm always
5382
5382
  carries an `id`, echoing the request's, because a result answers a request and
5383
5383
  a request always has one. The error arm is the only arm whose `id` may be
5384
5384
  missing, and it is then omitted — the member is absent from the envelope, never
@@ -5410,7 +5410,7 @@ transport` tables) is a real export of the mcp layer (`src/core` or
5410
5410
  metadata, run through the same dispatcher, and are projected back unstamped — so
5411
5411
  they inherit the modern engine's execution port, cancellation, bounds, and
5412
5412
  validation rather than running beside them. `initialize` → `{ protocolVersion,
5413
- capabilities: { tools: {} }, serverInfo: { name, version } }`, the version negotiated over the
5413
+ capabilities: { tools: {} }, serverInfo: { name, version } }`, the version negotiated over the
5414
5414
  legacy subset only: the client's `params.protocolVersion` is echoed when it is a
5415
5415
  supported legacy revision, and every other request — the modern `'2026-07-28'`,
5416
5416
  an unsupported revision, a non-string, or an absent one — falls back to the
@@ -5435,7 +5435,7 @@ capabilities: { tools: {} }, serverInfo: { name, version } }`, the version negot
5435
5435
  into a `success: false` result, the server adds no try/catch: that branch's
5436
5436
  `error` maps to `{ content: [{ type: 'text', text: <error> }], isError: true }`;
5437
5437
  a valued `success: true` branch maps to `{ content: [{ type: 'text', text:
5438
- JSON.stringify(value) }], structuredContent: value }`, carrying the value unchanged
5438
+ JSON.stringify(value) }], structuredContent: value }`, carrying the value unchanged
5439
5439
  alongside the backwards-compatible text. A value-less success retains the required
5440
5440
  empty text block and omits `structuredContent`.
5441
5441
  6. **One modern seam, subscriptions included, and `-32601` for anything off it.**
@@ -5469,7 +5469,7 @@ JSON.stringify(value) }], structuredContent: value }`, carrying the value unchan
5469
5469
  carries the same stamp. Ending the consumer's producer closes gracefully when the
5470
5470
  honoured filter omits `toolsListChanged`, with
5471
5471
  `{ resultType: 'complete', _meta: { 'io.modelcontextprotocol/subscriptionId': id,
5472
- … } }`. A stream honouring the tools family stays open until failure or signal abort.
5472
+ … } }`. A stream honouring the tools family stays open until failure or signal abort.
5473
5473
  The request id is only stream identity: a later request does not supersede
5474
5474
  an earlier one. The legacy method remains absent and answers `-32601`.
5475
5475
  7. **`handle` maps the boundary failures.** A `JSON.parse` throw (malformed
@@ -5520,7 +5520,7 @@ JSON.stringify(value) }], structuredContent: value }`, carrying the value unchan
5520
5520
  continuation, or principal — carrying the caught value the wire never sees; the
5521
5521
  emitter isolates a listener throw, routing it
5522
5522
  to its own `error` handler (the `error` option, surfaced as `(error,
5523
- event)`, not a domain event) — so a buggy observer can never corrupt a
5523
+ event)`, not a domain event) — so a buggy observer can never corrupt a
5524
5524
  dispatch, and a throwing `error` handler neither escapes nor recurses.
5525
5525
  11. **doc ↔ source method bijection.** The `## Methods` tables list exactly
5526
5526
  the public methods of each behavioral interface — `MCPServerInterface`,
@@ -5565,7 +5565,7 @@ event)`, not a domain event) — so a buggy observer can never corrupt a
5565
5565
  own JSON-parse `try`/`catch`) and draws a sharp line: a TRANSPORT-level
5566
5566
  failure — malformed JSON (`-32700`) or a parsed value that is not a
5567
5567
  JSON-RPC request (`-32600`, narrowed with `parseJSONRPCMessage` + `'method'
5568
- in request`, no `as`) — is HTTP **400** with a JSON-RPC error body carrying no
5568
+ in request`, no `as`) — is HTTP **400** with a JSON-RPC error body carrying no
5569
5569
  `id` member. A legacy dispatch result — success or in-band JSON-RPC error — is
5570
5570
  HTTP **200**. A modern result is **400** for `-32020` / `-32021` / `-32022`
5571
5571
  / `-32602`, **404** for `-32601`, and **200** otherwise; every notification
@@ -5620,7 +5620,7 @@ in request`, no `as`) — is HTTP **400** with a JSON-RPC error body carrying no
5620
5620
  consumer-provided list.
5621
5621
  13. **The client is the modern-only egress mirror (`src/core`).**
5622
5622
  `createMCPClient({ transport, identity?, capabilities?, version?, timeout?,
5623
- on? })` drives a remote server over an injected `MCPMessageTransportInterface`
5623
+ on? })` drives a remote server over an injected `MCPMessageTransportInterface`
5624
5624
  (transport-abstract, like the server). `connect()` issues a modern
5625
5625
  `server/discover` carrying `_meta` with the offered revision,
5626
5626
  client capabilities, and client identity. It intersects the peer's
@@ -5708,9 +5708,9 @@ on? })` drives a remote server over an injected `MCPMessageTransportInterface`
5708
5708
  `error` option, not a domain event). Consumers subscribe through `emitter.on`.
5709
5709
  15. **The HTTP client transport drives a remote server over `fetch`
5710
5710
  (`src/server`).** `createHTTPClientTransport({ url, headers?, fetch?,
5711
- timeout? })` returns a `MCPMessageTransportInterface` whose `send` POSTs one
5711
+ timeout? })` returns a `MCPMessageTransportInterface` whose `send` POSTs one
5712
5712
  JSON-serialized message to `url` with `content-type:
5713
- application/json` and an `Accept` of both `application/json` and
5713
+ application/json` and an `Accept` of both `application/json` and
5714
5714
  `text/event-stream` (plus any `headers`), then decodes the reply and
5715
5715
  emits each carried `JSONRPCMessage` on the `message` event: an
5716
5716
  `application/json` body is narrowed with `parseJSONRPCMessage`; a
@@ -5762,7 +5762,7 @@ application/json` and an `Accept` of both `application/json` and
5762
5762
  (`upgradeRequestPath`) is not `options.path` (default `DEFAULT_MCP_PATH`),
5763
5763
  the `Sec-WebSocket-Key` is absent, or the `Sec-WebSocket-Version` is not
5764
5764
  `13`. Otherwise it claims (returns `true`): `createNodeWebSocket({
5765
- socket, key, head, protocol })` (server mode → writes the `101` handshake,
5765
+ socket, key, head, protocol })` (server mode → writes the `101` handshake,
5766
5766
  selecting the configured subprotocol only when the client's offer contains it,
5767
5767
  and sends unmasked frames), wraps it in a `WebSocketServerTransport`, and
5768
5768
  pumps — each inbound `JSONRPCMessage` that `isJSONRPCRequest` runs
@@ -5831,7 +5831,7 @@ socket, key, head, protocol })` (server mode → writes the `101` handshake,
5831
5831
  (`src/server`).** `createMCPSession({ path?, ttl?, session?, clock?, origin?, keepalive? })`
5832
5832
  returns a `MiddlewareHandler<TState>` (`TState extends MCPSessionState`)
5833
5833
  that owns its own closure `Map<string, { session: MCPSession; touched:
5834
- number; version: MCPVersion }>` — no dependency on `@orkestrel/middleware` and no shared
5834
+ number; version: MCPVersion }>` — no dependency on `@orkestrel/middleware` and no shared
5835
5835
  session primitive; the store, mint, and validation are all native to
5836
5836
  this package. Compose it with `router.use(createMCPSession())` in front
5837
5837
  of a session-agnostic `createMCPRoutes(mcp)`; it owns its `path` (default
@@ -5875,7 +5875,7 @@ number; version: MCPVersion }>` — no dependency on `@orkestrel/middleware` and
5875
5875
  the log under a monotone base36 event id (returned), evicting the oldest
5876
5876
  past `capacity` + any entry older than the per-event TTL, and fans the
5877
5877
  message out to every `attach`ed open stream as `stream.write({ id, data:
5878
- JSON.stringify(message) })`. `session.replay(afterId)` returns every
5878
+ JSON.stringify(message) })`. `session.replay(afterId)` returns every
5879
5879
  retained log entry strictly after `afterId` in append order — an unknown
5880
5880
  / evicted cursor replays nothing. The `createMCPSession` middleware
5881
5881
  serves the resumable `GET {path}`: it validates the `mcp-session-id`
@@ -5999,7 +5999,7 @@ JSON.stringify(message) })`. `session.replay(afterId)` returns every
5999
5999
  contract over native host APIs (`src/browser`).**
6000
6000
  `createWebSocketClientTransport({ url, protocols? })` returns a
6001
6001
  `MCPMessageTransportInterface` whose `start()` opens `new WebSocket(url,
6002
- protocols)` and awaits the native `'open'` event (the RFC 6455 handshake
6002
+ protocols)` and awaits the native `'open'` event (the RFC 6455 handshake
6003
6003
  is the host's concern; a connection failure — the native `'error'` event
6004
6004
  while not yet `OPEN` — rejects `start()`); `send` writes each message as
6005
6005
  one text frame once `OPEN`, queuing (in order) any message sent before —
@@ -6051,7 +6051,7 @@ protocols)` and awaits the native `'open'` event (the RFC 6455 handshake
6051
6051
  (the implicit, portless channel — bound once, for the whole lifetime of
6052
6052
  the returned handle, so a dedicated worker's very first portless message
6053
6053
  needs no first-use setup), and registers one `scope.addEventListener(
6054
- 'message', …)` listener built by `createScopeMessageListener`. That one
6054
+ 'message', …)` listener built by `createScopeMessageListener`. That one
6055
6055
  listener handles every shape uniformly, per event, with no upfront
6056
6056
  detection flag: `event.ports.length > 0` spawns a fresh
6057
6057
  `createMessagePortTransport` + `bindServer` for that port (tracked for