Documentation
Migrate from Browserstation
Map all 40 reviewed wrapper tools to local Swarmtab operations.
This table maps all 40 names in the local Browserstation MCP wrapper reviewed on 17 October 2026. Its prefix is mcp__browserstation__; omit that prefix in the table. It is separate from upstream ReinforceNow/browserstation, which exposes /browsers and CDP WebSockets. Swarmtab nineteenth checkpoint has neither route compatibility nor a public CDP/Playwright transport. This guide is a rewrite map, not an installed compatibility adapter.
Session and result rules
Use Node 22+ and the checkout’s sdk/node/index.mjs, or Python 3.11+ and sdk/python on PYTHONPATH. Privately set SWARMTAB_URL and SWARMTAB_AGENT_TOKEN from Swarmtab’s agent connect information. The SDK creates a REST session on its first call, keeps Swarmtab-Session-Id, and closes with DELETE /v1/session. A wrapper s1 ID or upstream browser UUID cannot be reused. Keep your own wrapper-ID-to-client map if you maintain a compatibility layer.
Results keep MCP content, optional structuredContent and isError. Schema values live at structuredContent.data.data; check complete and validate before using them. Raw REST callers must check HTTP status and isError, including on HTTP 200. The SDK throws on either error layer. Default task lifetime is 600-second disconnected grace and 21,600-second maximum life, configurable by the human. Close clients explicitly; a timeout is an unknown action outcome, not permission to replay input.
Name-by-name mapping
Arguments below are tool JSON, used with client.call(name,args). Each client owns its session. Session arguments are therefore omitted from individual calls.
| Wrapper tool | Swarmtab tool and arguments | Difference / unsupported behavior |
|---|---|---|
browse |
For each URL: site {action:"scrape",url,format:"markdown",max_chars} |
No six-worker guarantee or matching multi-document envelope; use separate clients for isolation/concurrency |
browser_reset |
Close only the caller’s clients; create new ones | No agent-authorized global browser reset |
browser_status |
tabs {action:"list"} per owned client |
No cluster/worker status equivalent |
check |
check {ref,on:true} |
Supply on explicitly; wrapper default was true |
click |
click {ref} |
CSS must match exactly one element |
close_tab |
tabs {action:"close",id:"t2"} |
Resolve index through fresh tab list; last tab cannot be closed |
console_logs |
logs {kind:"console",limit:50} |
Use contains to filter bounded sanitized entries |
double_click |
click {ref,action:"double_click"} |
Same reference rules |
download |
click {ref}, then files {action:"downloads"} |
Agent-owned storage, not out_dir; hand off with approved Outbox |
drag |
click {ref:from_ref,action:"drag",to:to_ref} |
Or dx/dy; fresh references required |
eval_js |
read {format:"script",expression} |
Human/secret gates; not a raw CDP evaluator |
fill |
type {ref,text,submit:press_enter} |
Replaces value; typed:true emits sequential keys with bounded delay_ms and optional append:true |
get_cookies |
space {action:"export_cookies",path:"login.json"} |
Human approval, scoped file; no raw cookie JSON return |
go_back |
nav {action:"back"} |
Resubmission gates remain |
go_forward |
nav {action:"forward"} |
Resubmission gates remain |
hover |
click {ref,action:"hover"} |
Same reference rules |
list_frames |
read {format:"controls"} for combined frame controls |
No matching frame-index catalog or selected-frame mode |
list_tabs |
tabs {action:"list"} |
IDs such as t1, not zero-based indexes |
navigate |
open {url} |
Read new controls after navigation |
network_requests |
logs {kind:"network",limit:50} |
Sanitized request/status/failure entries; supports contains and only_failures; approved HAR is separate |
new_tab |
tabs {action:"new",url}, then tabs {action:"list"} |
New returns the page read; list tabs to look up its ID (such as t2) before switching or closing |
page_head |
read {format:"seo"} |
Swarmtab SEO shape |
page_html |
read {format:"html",selector,max_bytes} |
max_chars is not the HTML byte limit; live DOM and masked hidden/secret values |
page_read |
read {format:"controls",max_chars} |
Fresh control numbers start at zero, not wrapper’s one |
press |
press {key} |
Swarmtab key syntax; not a fill helper |
reload |
nav {action:"reload"} |
Guarded reload; input outcome must be inspected |
right_click |
click {ref,action:"right_click"} |
Same reference rules |
screenshot |
New client: open {url}, optional settings {viewport:"1440x900"}, then screenshot {full:true} |
Decode inline image in caller; width, out_path, full_page are not aliases |
scroll |
scroll {to:"bottom"} or {to:"down",n:600} / {to:"up",n:600} |
to required, not defaulted; control scroll is {to:"control",n:<number>}, no CSS ref |
select |
select {ref,value} |
Native matching tries value before visible text; wrapper precedence was opposite; arrays support multiple selection |
session_close |
client.close() / DELETE /v1/session |
Ends only this client; private state and unfinished jobs are removed |
session_list |
Caller maintains client/session registry | No agent-wide list of other clients’ sessions |
session_open |
New SDK client, optional viewport setting, then open {url} |
No worker allocation; use the actual ephemeral local port |
session_screenshot |
screenshot {full:full_page} |
Inline JPEG by default or format:"png", rather than writing out_path; supports scale and freeze_animations; cross-origin frame consent may be required |
set_cookies |
space {action:"import_cookies",path:"login.json"} |
Approved file import; raw cookies string is unsupported |
storage |
read {format:"storage"} |
Both local/session stores, sensitive names masked; no selected-frame inheritance |
switch_tab |
tabs {action:"switch",id:"t1"} |
Resolve current index to ID rather than adding one |
upload_file |
files {action:"upload",ref:<number>,path} or {action:"upload",ref,from:"inbox",name} |
paths or inbox names selects up to 20 admitted files atomically, up to 200 MiB; multiple files require a multiple input. Repeated calls replace selection |
use_frame |
Use fresh numbered frame controls from read |
No sticky frame scope for CSS, HTML, storage, scripts or extraction |
wait_for |
wait {text,seconds:10} |
timeout_s maps to seconds; selector-visible wait has no exact equivalent; count is not visibility |
Both systems require fresh numbered references, but numbers cannot transfer. A string containing only digits is also a Swarmtab control number. CSS targets traverse open shadow roots in the main document, must be visible/enabled, and must match exactly once. Closed roots and general selected-frame scope remain unsupported. Use numbered controls for represented frame/shadow controls. Ordinary logs retain 200 entries; the default read is 50.
Jobs, pagination and timeouts
Swarmtab’s ordinary site(crawl) and site(batch) return a textual job ID. Poll site {action:"status",job} in the same session; Node waitJob returns the MCP status result, not all original documents. Original results are retained separately from the 200-page scrape cache, up to 16 MiB per job and 50 MiB per agent. Page site(status) with offset/limit or SDK jobResults / job_results. Saved-space batches publish batch.jsonl; private batches keep results in memory without an artifact. Deterministic schema crawls run as background jobs up to 5,000 pages, and schema batches accept 1–100 URLs without chunking. Saved-space schema crawls keep resumable checkpoints. Actual client-model sampling keeps its 16-page, 180-second collection limit.
Map continuation uses site {action:"map",cursor}. A cursor is agent/context/query-bound, expires after five minutes and is replaced by a new map. Continue until the cursor is null, regardless of limited. Rows and monitors use offsets instead.
| Operation | Timeout / retry contract |
|---|---|
| Wrapper navigation | 45-second Playwright calls in the reviewed wrapper |
| Swarmtab page operation | Typical 30-second page limit; eligible engine GET recovery can retry once |
| Input | Not automatically replayed; inspect after a timeout |
| Node SDK HTTP request / polling | Default request timeout 240,000 ms; polling timeout/interval are milliseconds |
| Python SDK HTTP request / polling | Default request timeout 240 seconds; polling timeout/interval are seconds |
| Text wait | seconds 1–60; not a selector-visible wait |
| Assertion wait | timeout_ms 0–60,000; default 10,000; count does not imply visibility |
Runnable form migration
Save this as a root-level .mjs file in the checkout, set FORM_URL to a page with #name and #agree, then run with node. It fills a local form without submitting it. Runnable equivalent: node examples/integrations/migrate.mjs browserstation "$FORM_URL".
import { Swarmtab } from './sdk/node/index.mjs';
const client = new Swarmtab({ baseUrl: process.env.SWARMTAB_URL, token: process.env.SWARMTAB_AGENT_TOKEN });
try {
await client.open(process.env.FORM_URL);
await client.call('type', { ref: '#name', text: 'Migration visitor' });
await client.call('check', { ref: '#agree', on: true });
const result = await client.read({ format: 'markdown', max_chars: 2000 });
console.log(result.content.filter(c => c.type === 'text').map(c => c.text).join('\n'));
} finally {
await client.close();
}
Swarmtab runs on the Mac, so use http://127.0.0.1:<fixture-port> for local development, with the human’s allow_local grant. Do not preserve the wrapper’s Docker host.docker.internal workaround. Saved logins use named spaces, exclusive ownership and human consent. Ask the human for login, 2FA, CAPTCHA and approvals; no solver is provided. Real browser mode and identity remain the owner’s choice. Browser-use and CrewAI custom-tool recipes are covered in the companion integration guide; they do not restore raw CDP compatibility.