Skip to main content

Design: the dev MCP server#

Status: accepted (2026-10-18).

Why#

The three MCP servers (cdp, driver, sb) drive a browser. A model that writes tests with this library, runs sbrs, or finds out why a session fails needs other things too: which method does what, how a Python call maps to Rust, what a flag means, what an error means and how to fix it, whether the machine and the settings are fine. None of that needs a browser.

Every tool costs context in every session that loads the server (sb has 104 tools, and some clients stop near 40 MCP tools in all). So these tools are not added to the browser servers. They are a fourth server, dev, that starts no browser:

A user installs dev plus one browser server. The browser servers stay as they are, except that the driver server gets the session debugging tools the other two already have (see the end).

The dev server#

Twelve tools. The tools that need nothing from the machine except the embedded data are read-only and open-world false.

ToolInputResultEffect
find_apiquery, engine (basecase, cdp, any), limitentries: name, owner type, signature, first paragraph of the doc comment, is_async, deprecatedread-only
parity_lookuppython_namethe Python SeleniumBase 4.55.2 name, status, Rust path, notes; close matches when there is no exact oneread-only
python_to_rustcode, source (auto, seleniumbase, selenium), test_namethe Rust test and the importer’s diagnosticsread-only
scaffoldkind (test, suite), name, url, basicthe files sbrs mkfile / sbrs mkdir create, as path and content; nothing is writtenread-only
cli_helpcommandwith no command, the commands and global flags; with one, its usage, flags, defaults and aliasesread-only
list_pluginsnonethe built-in plugins of this buildread-only
plugin_infonamewhat one plugin declares and may doread-only
install_driverbrowser (chrome, edge, firefox), versionthe path and version installeddownloads, open-world
explain_errormessage, categorycategory, the library’s hint, causes and fix steps, the exit status sbrs usesread-only
check_configpathevery problem in the settings file and the SB_* variables, and what the settings would startread-only
check_fingerprintjsonwhether a fingerprint or a profile payload parses, and its errors and warningsread-only
diagnose_environmentnoneChrome, driver, their versions, the driver cache, the output folder, the features of this buildread-only

diagnose_environment is the tool the cdp and sb servers already have; the dev server registers the same code.

Where each answer comes from#

  • find_api searches an index generated from the source. A test parses every file under src/ with syn, takes the public methods of the inherent impl BaseCase blocks (src/api/base_case.rs and src/api/base_case_impls/*.rs hold most) and, in src/sb_cdp/, of Browser, Page, Locator and BrowserContext, and writes src/mcp/dev/api_index.json; the server embeds the file with include_str!. A second test fails when the committed file differs from a fresh one, and SB_UPDATE_API_INDEX=1 cargo test rewrites it (the same scheme as SB_UPDATE_COMPLETIONS for the usage spec). Ranking: exact name, then name contains the query, then every word of the query in the name, then every word in name, owner or doc. The signature is the text of the source, not a re-print, so it reads as written.
  • parity_lookup reads parity/api.toml, embedded.
  • python_to_rust calls utilities::python_importer::import_python. The Python is parsed, not run. The input is capped at 256 KiB.
  • scaffold calls Suite::files() and TestFile::render(), the same templates the commands write, through the same name validation (RelativeName). sbrs mkchart and sbrs mkpres write HTML straight to a file and have no renderer, so they are not offered.
  • cli_help reads completions/sbrs.usage.kdl, embedded: the usage spec the usage derives write from the declarations of the command line. A test of the CLI crate fails when it is stale, so it cannot drift from the parser, and sbrs --help, the shell completions and the CLI reference page come from the same spec. The library may not depend on a command-line crate (tests/library_manifest.rs), so usage-lib cannot read it there: src/mcp/dev/spec.rs reads the subset of KDL the spec uses (nodes, arguments, properties, child blocks, quoted, raw and multi-line strings, #true, #false, #null, comments) into commands, flags and arguments, and src/mcp/dev/help.rs renders them as text. The tool reads the spec and not the rendered reference page because the page is a layout that changes (it was restyled while this tool was written), and the spec is the data.
  • list_plugins, plugin_info call cli::plugin_cmd::list and info. Without the feature plugins-builtin they say the build has no plugin and how to get them (--features plugins-builtin).
  • install_driver calls cli::scripts::sb_install::install_driver, which sbrs install calls, so it fills the same downloaded_drivers/ folder of the server’s working directory. The downloader is a parameter of the server, so the tests install through a fake one and never reach the network.
  • explain_error rebuilds a SeleniumBaseError of the category (and the payload the message carries) and returns its category(), hint() and exit_code(), then the matching sections of docs/help/common_problems.md, embedded and cut by heading.
  • check_config loads the settings as sbrs does (the file, then the SB_* variables, then validation) and reports every problem instead of the first, then the runtime variables (RuntimeConfig::from_env_lenient) and the plugin selection (check_selection). It starts nothing.
  • check_fingerprint parses the JSON as a Fingerprint, or as a profile payload (ProfileParams::to_fingerprint), and returns what Fingerprint::validate finds. validate_fingerprint of the sb server uses the same function for its report.

Safety#

  • Every tool but install_driver is read-only, and says so in its annotations. install_driver is not read-only, replaces a driver of another version in the cache, and reaches the network, so its annotations are readOnly=false, destructive=true, idempotent=true, openWorld=true (the new Effect::Fetch).
  • scaffold writes nothing; the model writes the files with its own tools, which the client already guards.
  • check_config reads one file. The path must be relative, inside the folder the server runs in, with no ... The values it prints are redacted the way diagnose_environment redacts them (credentials in a proxy URL, anything named like a secret), and a parse error does not quote the offending line of the file or the value it refused, since the TOML parser’s message does both.
  • python_to_rust and check_fingerprint read text the model sends; both bound its size and neither runs it.
  • install_driver takes the browser and version from a closed set and a parsed DriverVersion; no URL or path comes from the model.
  • A result says what it did. An answer from a build without a feature says so; a search that finds nothing says it searched the index of this version.

Debugging, by symptom#

SymptomTool
”element not found”, a wait times outdiagnose_selector, suggest_selectors, inspect_page (browser server)
The page is blank or wronginspect_page, get_console_messages, get_failed_requests, capture_debug_bundle
The driver and Chrome disagree (“session not created”)diagnose_environment, then install_driver
A site blocks the sessioncheck_fingerprint, validate_fingerprint, audit_page
An error text you do not understandexplain_error
A setting is ignored or refusedcheck_config
A flag or commandcli_help

The driver server#

The driver server mirrors upstream’s 26 Driver() tools. It also gets the tools below, which sb already has and which run on BaseCase unchanged: they are the same ToolDefs, registered by name from the shared table in devtools.rs, not copies.

inspect_page, suggest_selectors, diagnose_selector, get_console_messages, capture_debug_bundle, get_page_info, export_session_as_test and diagnose_environment.

get_failed_requests and audit_page stay with cdp and sb. The WebDriver engine has no network events to record, so get_failed_requests can only read the performance timeline there; the driver server is kept at 34 tools.

Not in scope#

No new CLI flag. sbrs-mcp reads the server name through Profile::from_str, so --server dev works once Profile::Dev exists; only the help text of the flag names the new value.