Skip to main content

ServerState

Struct ServerState 

Source
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: SidecarConfig

Sidecar 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

Source

pub fn new(browser: ResolvedBrowser) -> Self

Source

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).

Source

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.

Source

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.

Source

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.

Source

pub async fn browser_snapshot(&self) -> ResolvedBrowser

Snapshot the current resolved browser (cheap clone of a small struct).

Source

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.

Source

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.

Source

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.

Source

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:blank and store it.
  • Set but dead (no longer in live_target_ids) → recreate about:blank and re-point the pointer. This is the scratch-style implicit recovery for the server-owned active tab; explicit tabs created via browser_tab_new / browser_tab_select also travel through here once they become the active tab, but recovery there means the agent-named tab is gone — see browser_tab_select’s dead-tab handling for the explicit-select contract.
  • Set and alive → return as-is.
Source

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.

Source

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.

Source

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).

Source

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

Source§

fn clone(&self) -> ServerState

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ServerState

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more