pub struct ServerState {
pub browser: Arc<RwLock<ResolvedBrowser>>,
pub bidi: BidiCache,
pub bidi_lock: Arc<Mutex<BidiLockState>>,
pub backend: Arc<Mutex<Option<TabBackend>>>,
pub active_target_id: Arc<Mutex<Option<String>>>,
pub origin_target_ids: Arc<Mutex<HashMap<String, String>>>,
pub sidecar: Arc<Mutex<Option<Sidecar>>>,
pub sidecar_config: SidecarConfig,
pub op_barrier: Arc<RwLock<()>>,
}Expand description
State carried by the server. Tools reach into this for the resolved browser endpoint and any cached engine clients.
browser is RwLock-wrapped so browser_select can swap the active
browser at runtime; readers take a brief read lock and clone out the
value they need (the struct is cheap to clone).
active_target_id is the in-memory pointer to the MCP server’s
“current tab” — replaces the SQLite _mcp-<pid> row pattern. The
pointer is lazy-initialised on first stateful tool call and updated
by browser_tab_* and browser_select.
Fields§
§browser: Arc<RwLock<ResolvedBrowser>>§bidi: BidiCache§bidi_lock: Arc<Mutex<BidiLockState>>Firefox BiDi single-session lock, acquired lazily on first tool
call and held for the server’s lifetime. None for CDP browsers
and external endpoints (where acquire_bidi_lock_if_needed
returns None) — the inner Option<BidiLockGuard> distinguishes
“haven’t tried yet” from “tried, not applicable” via the outer
Mutex being unlocked vs returning None.
backend: Arc<Mutex<Option<TabBackend>>>Cached TabBackend for the configured browser, opened lazily
on first tool call and reused for the server’s lifetime. Avoids
repeatedly running the BiDi session.new handshake and lets us
share one CDP WebSocket across all tool calls.
active_target_id: Arc<Mutex<Option<String>>>In-memory “active tab” pointer. None until lazy-init by
current_tab() or set explicitly by browser_tab_select /
browser_tab_new. Cleared on browser_tab_close (when closing
the active tab) and on browser_select.
origin_target_ids: Arc<Mutex<HashMap<String, String>>>MCP-owned origin tabs created by bare browser_fetch, keyed by
requested origin root (https://example.com/). This supplements
URL-based live-target matching so a tab that redirects to a login
origin after token expiry is still reused on later fetches instead
of creating one new tab per retry.
sidecar: Arc<Mutex<Option<Sidecar>>>Lazy-spawned Playwright sidecar for the Chromium-only interaction
tools (browser_click, browser_snapshot, etc.). One sidecar
per server-per-browser; browser_select disposes the old one
and the next sidecar-using tool spawns a fresh one against the
new endpoint. None for BiDi browsers (the sidecar tools error
with EngineUnsupported) and on fresh servers until first use.
sidecar_config: SidecarConfigSidecar config (Playwright version override etc.) — set once at server startup from CLI args, read on each sidecar spawn.
op_barrier: Arc<RwLock<()>>Operation barrier. Non-exclusive tool calls acquire a read
guard so they can run concurrently; switch_browser acquires a
write guard which waits for all in-flight tool operations to
finish, preventing the old backend / BiDi session from being
torn down while another tool is still using it.
browser_select is the only tool that needs exclusive access
(via switch_browser); handle_tools_call skips the read
guard for it to avoid deadlocking with its own write guard.
Implementations§
Source§impl ServerState
impl ServerState
pub fn new(browser: ResolvedBrowser) -> Self
Sourcepub fn with_sidecar_config(
browser: ResolvedBrowser,
sidecar_config: SidecarConfig,
) -> Self
pub fn with_sidecar_config( browser: ResolvedBrowser, sidecar_config: SidecarConfig, ) -> Self
Construct a ServerState with a non-default sidecar config (e.g.
a custom Playwright version from --playwright-version).
Sourcepub async fn ensure_sidecar(&self, tool_name: &str) -> Result<Sidecar>
pub async fn ensure_sidecar(&self, tool_name: &str) -> Result<Sidecar>
Lazy-spawn the Playwright sidecar against the current browser.
Errors with EngineUnsupported when the active browser is BiDi
(Playwright can’t drive a user-launched Firefox over BiDi/CDP).
Idempotent: subsequent calls return the cached handle. The handle
is dropped (and the child killed) when switch_browser clears it.
Sourcepub async fn ensure_sidecar_supported(&self, tool_name: &str) -> Result<()>
pub async fn ensure_sidecar_supported(&self, tool_name: &str) -> Result<()>
Validate that the active browser can use the Playwright sidecar without spawning Node or opening a Playwright CDP connection.
Sourcepub async fn reset_sidecar(&self)
pub async fn reset_sidecar(&self)
Drop the cached Playwright sidecar after a connection-layer failure.
Drop kills the child through SidecarInner; avoid a best-effort
dispose RPC here because the sidecar may be exactly what is wedged.
Sourcepub async fn browser_snapshot(&self) -> ResolvedBrowser
pub async fn browser_snapshot(&self) -> ResolvedBrowser
Snapshot the current resolved browser (cheap clone of a small struct).
Sourcepub async fn ensure_bidi_lock(&self) -> Result<()>
pub async fn ensure_bidi_lock(&self) -> Result<()>
Ensure the BiDi single-session lock is held (if applicable). Lazy + idempotent: called by each tool handler before opening a BiDi session, returns immediately on second+ calls.
Sourcepub async fn ensure_backend(&self) -> Result<TabBackend>
pub async fn ensure_backend(&self) -> Result<TabBackend>
Lazy-open (or return cached) TabBackend for the server’s
browser. Acquires the BiDi lock first if applicable. The backend
is cached for the server’s lifetime so the BiDi session.new
handshake runs once and the CDP WebSocket is reused across calls.
Sourcepub async fn ensure_active_browser_alive(&self) -> Result<()>
pub async fn ensure_active_browser_alive(&self) -> Result<()>
Check that the active registered browser is still usable before a tool
attempts protocol I/O. This keeps terminated-browser recovery
actionable for agents: call browser_start to launch/reuse a browser
or browser_select to switch to another live one.
External URL endpoints have no registry identity, so they are checked by the transport layer.
Sourcepub async fn current_tab(&self) -> Result<(TabBackend, String)>
pub async fn current_tab(&self) -> Result<(TabBackend, String)>
Resolve the MCP server’s “active tab” — backed by an in-memory
active_target_id pointer rather than a SQLite row.
The returned (backend, target_id) is the routing pair stateful
MCP tools (browser_navigate, browser_get_html, …) use when no
explicit tab / target arg is given.
Behaviour:
- None → create an
about:blankand store it. - Set but dead (no longer in
live_target_ids) → recreateabout:blankand re-point the pointer. This is the scratch-style implicit recovery for the server-owned active tab; explicit tabs created viabrowser_tab_new/browser_tab_selectalso travel through here once they become the active tab, but recovery there means the agent-named tab is gone — seebrowser_tab_select’s dead-tab handling for the explicit-select contract. - Set and alive → return as-is.
Sourcepub async fn resolve_or_create_for_origin(
&self,
url: &str,
) -> Result<(TabBackend, String)>
pub async fn resolve_or_create_for_origin( &self, url: &str, ) -> Result<(TabBackend, String)>
Resolve or create an MCP-owned tab for a fetch URL’s origin.
TabBackend::resolve_or_create_for_origin can only reuse targets
whose current browser URL still has the requested origin. During auth
expiry, an origin tab may redirect to an identity provider or login
route; if we only inspect current URLs, each retry can create another
tab. This cache records the target originally allocated for each
requested origin and reuses it while it is still live.
Sourcepub async fn resolve_target_for_args(
&self,
args: &Value,
) -> Result<(TabBackend, String)>
pub async fn resolve_target_for_args( &self, args: &Value, ) -> Result<(TabBackend, String)>
Route a stateful tool call to a backend + target id based on the
optional tab (named) and target (URL regex) args. tab and
target are mutually exclusive. Falls through to current_tab()
when neither is provided.
For the named-tab path: the registered tab row is resolved (with
sweep-on-read for stale rows) and returned. Tools that want
recover-on-failure semantics should structure their op around the
returned (backend, target_id) — full with_named_tab_recovery
can’t run from a Send MCP future because Registry is !Send.
For the URL-regex path, probe-and-iterate via the live targets
snapshot. Surfaces SessionError::TabHung if every match is
unresponsive within a 500ms probe.
Sourcepub async fn registered_browser_name(&self) -> Result<String>
pub async fn registered_browser_name(&self) -> Result<String>
The registered browser’s name. Errors if the active browser is an external URL endpoint (no stable identity for named tabs).
Sourcepub async fn switch_browser(&self, new_browser: ResolvedBrowser) -> Result<()>
pub async fn switch_browser(&self, new_browser: ResolvedBrowser) -> Result<()>
Swap the active browser. Drops the cached backend and BiDi session, releases the BiDi lock (if held), clears the active tab pointer, then installs the new browser and re-acquires the BiDi lock if the new one needs it. The next stateful tool call lazy-opens the new backend.
§Concurrency
The caller must hold a write guard on Self::op_barrier to
ensure no concurrent tool call is still using the old backend.
[handle_tools_call] acquires the write guard for browser_select
before invoking this method.
Trait Implementations§
Source§impl Clone for ServerState
impl Clone for ServerState
Source§fn clone(&self) -> ServerState
fn clone(&self) -> ServerState
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl !RefUnwindSafe for ServerState
impl !UnwindSafe for ServerState
impl Freeze for ServerState
impl Send for ServerState
impl Sync for ServerState
impl Unpin for ServerState
impl UnsafeUnpin for ServerState
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more