umbra
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| UMBRA_API_KEYS | No | Comma-separated list of API keys for HTTP transport authentication (optional for stdio). | |
| UMBRA_NO_CLOAK | No | Set to '1' to disable CloakBrowser and use stock Chromium. | 0 |
| UMBRA_CONTAINER | No | Set to '1' to indicate containerized environment (recommended in MCP stdio setup). | 1 |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
| logging | {} |
| prompts | {
"listChanged": true
} |
| resources | {
"subscribe": false,
"listChanged": true
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| spawnA | Open stealth tab. browser_id='alice'=isolated Chrome (own cookies/identity, ~1.5s boot).
For same-identity new pages prefer Proxy: pass Ex: spawn('https://news.ycombinator.com') → {"tab_id":"t0","browser_id":"default","url":"..."} Ex: spawn('about:blank', browser_id='alice', proxy='http://1.2.3.4:8080') Ex: spawn(use_proxy_pool=True, proxy_country='US', browser_id='scraper-1') chromium: 'cloak' (default) auto-downloads CloakBrowser's patched chromium (C++ fingerprint patches — beats JS shims). 'stock'=system chromium. Pass an absolute path to use a custom binary. Env: UMBRA_NO_CLOAK=1 forces stock; UMBRA_CLOAK_BINARY= uses that as cloak. See cloak_status. |
| proxy_pool_loadA | Bulk-load proxies into the pool. format='lines': one URL per line ( rotation strategies: round_robin (default), random, least_used, best_health, sticky_browser (same browser_id always gets same entry). Ex: proxy_pool_load(data='http://u:p@gw1:8080\nhttp://u:p@gw2:8080') Ex: proxy_pool_load(data='/path/to/proxies.csv', format='csv') |
| proxy_pool_addB | Add ONE proxy to the pool.
Ex: proxy_pool_add('http://gw.proxy.com:8080', username='u123', password='p', country='US') |
| proxy_pool_removeA | Remove one entry by id. Use proxy_pool_list to see ids. Ex: proxy_pool_remove('a1b2c3d4') → {"removed":true,"total":4} |
| proxy_pool_clearA | Drop ALL entries. Stickies cleared. Rotation strategy preserved. Ex: proxy_pool_clear() → {"cleared":12} |
| proxy_pool_listB | List all entries (creds redacted by default). Columnar. Ex: proxy_pool_list() → {"rotation":"round_robin","entries":[...]} |
| proxy_pool_health_checkA | Probe every entry via tls_fetch-through-proxy + report rolling health. NB: tls_fetch uses curl_cffi which only honors HTTP/HTTPS proxies, not SOCKS5. SOCKS5 entries get a single connect-test instead. Ex: proxy_pool_health_check() → {"checked":12,"alive":10,"results":[...]} |
| proxy_pool_exportA | Dump pool state — round-trippable via proxy_pool_load(format='json'). redact=False (default) emits real creds; redact=True for safe sharing. Ex: proxy_pool_export() → {"rotation":"...","entries":[{...creds...}]} |
| closeA | Close a tab. Browser stays for other tabs. Free RAM after one-off tasks. Ex: close('t0') → {"closed":true} |
| list_browsersA | List browser instances + tab counts. Audit parallel sessions. Ex: list_browsers() → {"browsers":[{"browser_id":"default","tab_count":2,"tab_ids":["t0","t1"]}]} |
| kill_allA | Nuke ALL browsers/tabs/handoffs/hooks. Last resort — prefer close/close_browser. Ex: kill_all() → {"browsers":3,"tabs":7,"handoffs":1} |
| close_browserB | Close all tabs in a browser + stop the Chrome process. Use after dedicated isolated session. Ex: close_browser('alice') → {"closed_browser":"alice","closed_tabs":["t3","t4"]} |
| list_tabsA | List open tabs w/ URL+title+browser_id. Cheap. Ex: list_tabs() → {"tabs":[{"id":"t0","browser":"default","url":"https://...","title":"..."}]} |
| switch_tabA | Focus a tab. Needed for visual capture; other tools work on background tabs. Ex: switch_tab('t1') → {"focused":"t1"} |
| navigateA | Navigate tab. Stealth payload + cookies persist. Cheaper than spawn — default reflex. timeout_s: hard cap on load (default 30s). Raises on timeout (tab survives, retry/abort). Ex: navigate('t0', 'https://example.com/login') → {"url":"..."} |
| backB | History.back(). When: undo a nav step (e.g. clicked wrong link, retry from prev page). |
| forwardB | History.forward(). When: redo a nav step after |
| reloadA | Reload page. hard=True bypasses cache (CDN debug, fresh build). Ex: reload('t0') → {"reloaded":true} |
| aria_snapshotA | ARIA tree of interactive elements w/ Ex: aria_snapshot('t0') → {"tree":"[0] button "Sign in"\n[1] textbox "email"\n...","count":12} |
| aria_clickA | Click ARIA element by idx (zero mouse, semantic). Always prefer over Ex: aria_click('t0', 3) → {"ok":true} |
| aria_typeA | Type into ARIA input by idx. humanize=True (default)=log-normal keystroke +
pair-classification (~80-150ms/char, defeats cadence detectors). False=instant
CDP keys (detectable). For multi-field use Ex: aria_type('t0', 1, 'me@example.com') → {"ok":true} |
| find_by_textA | Fuzzy "the button/link that says X" → ARIA idx, in one call. Skip aria_snapshot. role_hint='button'|'link'|'textbox' to disambiguate. Ex: find_by_text('t0', 'Sign in', role_hint='button') → {"idx":3,"found":true} |
| fill_formA | Fill N inputs from {label:value} in one call. Labels fuzzy-match placeholder/ aria-label/. Beats N aria_type round-trips. Ex: fill_form('t0', {'email':'a@b.c','password':'hunter2'}) → {"filled":["email","password"],"missed":[]} |
| current_stateA | Cheap orientation: URL+title+h1/h2+forms+interactive_count. Lighter than aria_snapshot.
Good first move after Ex: current_state('t0') → {"url":"...","title":"...","h1_h2":[...],"forms":[...],"interactive_count":42} |
| click_atA | Pixel click for captcha/canvas/PDF where ARIA misses. Coords from screenshot/ inspect_element rect. Default to aria_click — pixel is fragile. Ex: click_at('t0', 320, 480) → {"ok":true} |
| press_keyA | Press key (Enter/Esc/Tab) + optional modifiers ['ctrl','shift','alt','meta']. Form submit, dismiss modal, Ctrl+K palette. Ex: press_key('t0', 'Enter') / press_key('t0', 'k', modifiers=['ctrl']) |
| scrollA | Scroll by (dx,dy), to_bottom=True, or scrollIntoView selector. Lazy-load / reveal. Ex: scroll('t0', to_bottom=True) / scroll('t0', selector='#footer') → {"ok":true} |
| paste_textA | Instant paste via CDP (no humanize, ~1000x faster). For big blobs on trusted pages. Skip on hostile sites (cadence-detectable). Ex: paste_text('t0', long_blob) → {"ok":true,"len":1234} |
| hoverB | Hover at (x,y) — reveals :hover menus, tooltips, hover-to-show buttons. Ex: hover('t0', 200, 100) → {"ok":true} |
| select_optionA | Set HTML .value + change/input events. For custom dropdowns use aria_click. Ex: select_option('t0', '#country', 'BR') → {"ok":true} |
| wait_forA | Wait — one of: selector / url_contains / network_idle_ms. For text use Ex (DOM): wait_for('t0', selector='.submit-btn') → {"ok":true,"why":"selector_found"} Ex (URL): wait_for('t0', url_contains='/dashboard') → {"ok":true,"why":"url_match","url":"..."} Ex (network): wait_for('t0', network_idle_ms=500) → {"ok":true,"why":"network_idle"} |
| extract_textA | innerText of selector. pierce walks shadow+same-origin iframes. denoise (default)
strips zero-width chars + collapses 3+ blank lines lossless. For full articles
prefer Ex: extract_text('t0','.article',2000) → {"_untrusted":true,"text":"Body…"} Ex: extract_text('t0','body',8000,denoise=False) → byte-exact |
| extract_linksB | Visible links, columnar + URL footnoting ( Ex: extract_links('t0', same_origin=True) → {"_untrusted":true,"links":[...],"_refs":{"1":"https://x.com"}} where rows=[text,ref,path] |
| grep_textB | Regex-hunt page text. Cheaper than extract_text+scan. Returns [{match,context,line}]. Ex: grep_text('t0', 'API_KEY=\w+') → {"_untrusted":true,"matches":[{"match":"API_KEY=abc","context":"...","line":42}]} |
| dom_queryB | querySelectorAll → element list w/ attrs (href/data-*/src/rect). pierce walks shadow + iframes. Columnar-compressed. Ex: dom_query('t0', 'a.btn', 5) → {"_untrusted":true,"elements":{"_columnar":true,"keys":["tag","href","rect"],...}} |
| upload_fileA | Set .files (skip OS picker). Paths absolute on server FS. Security: if UMBRA_UPLOAD_ROOT env is set (colon-sep allowlist of dirs), every path must resolve under one of those roots. Unset → no restriction (legacy default; set the env in any agent-driven deployment). Ex: upload_file('t0', 'input[type=file]', ['/abs/img.png']) → {"ok":true} |
| setup_downloadsA | Allow + redirect downloads to dir. Call BEFORE click that downloads (else silent
block). Pair w/ Ex: setup_downloads('t0', '/tmp/dl') → {"download_dir":"/tmp/dl"} |
| wait_for_downloadB | Wait for new file in dir, return path once size stable. Pair w/ Ex: wait_for_download('t0', '/tmp/dl', 30) → {"path":"/tmp/dl/file.pdf","size_bytes":102400,"name":"file.pdf"} |
| wait_for_textB | Wait until Ex: wait_for_text('t0', 'Welcome back', timeout_s=10) → {"ok":true,"found_in_ms":1840} |
| inspect_elementB | Attrs + computed style + rect of one element. For debug, click_at coords, audit. Ex: inspect_element('t0', '#submit') → {"_untrusted":true,"tag":"button","attrs":{...},"rect":{x,y,w,h},"computed":{...}} |
| screenshotA | JPEG → base64 (q=65 cheap). For VLM visual reasoning. For text use extract_markdown. Ex: screenshot('t0', full_page=True) → {"b64":"...","fmt":"jpeg","len":48201} |
| screenshot_regionA | JPEG of a (x,y,w,h) region. For captcha tiles, isolated VLM crops. Rect from inspect_element. Ex: screenshot_region('t0', 100, 200, 300, 100) → {"b64":"...","fmt":"jpeg","rect":{x,y,w,h}} |
| evaluateA | Run JS in page (last-resort escape hatch). Prefer dom_query/extract_text/aria_*. Ex: evaluate('t0', 'document.title') → {"_untrusted":true,"result":"Hacker News"} Ex: evaluate('t0', 'fetch("/api/me").then(r=>r.json())', await_promise=True) |
| inject_cssB | Inject , persists page lifetime. Hide cookie banner/paywall/modal, highlight for screenshot. Ex: inject_css('t0', '.cookie-banner{display:none!important}') → {"injected":true} |
| extract_markdownA | Page → clean MD (Readability + markdownify). DEFAULT for "read this page". Keeps
headings/lists/code/links. content_only skips nav/footer/ads (fallback to body).
Needs Ex: extract_markdown('t0') → {"_untrusted":true,"markdown":"# Title\n\nlink...","title":"...","source_html_len":4521} Ex: extract_markdown('t0', selector='.main-content', max_chars=50000) |
| clone_elementA | Pixel-approx clone: DOM+CSS+asset URLs+renderable Ex: clone_element('t0', '.product-card') → {"_untrusted":true,"html":"...","css":"...","doc":"","assets":[...]} |
| get_console_logsA | Buffered console output since tab spawn. First-line check when page misbehaves. Ex: get_console_logs('t0') → {"_untrusted":true,"logs":[{"level":"error","text":"..."}]} |
| get_network_requestsB | Recent requests by the page. Discover hidden APIs (then Ex: get_network_requests('t0') → {"_untrusted":true,"requests":[{"id":"...","url":"...","method":"GET","type":"XHR"}]} |
| get_response_bodyA | Fetch a buffered HTTP response body by request_id. _untrusted=true (server-controlled). When: paired with |
| memory_metricsB | Performance counters: JS heap size, DOM nodes, layout count, etc. When: profiling a slow page, hunting a memory leak across a long
automation run, or deciding whether to |
| clear_cookiesA | Wipe ALL cookies. Returns count cleared. When: testing logged-out flow, simulating a fresh visitor without spawning a new browser, or recovering from a stuck auth state. Note: also wipes OAuth/SSO state — re-login required after. |
| block_urlsB | Block URL patterns (wildcards). [] to clear. Speed up + skip trackers/heavy assets. Ex: block_urls('t0', ['googletag', '*.gif']) → {"blocked_patterns":2} |
| set_extra_headersB | Inject headers into every outgoing request. Auth, A/B flags, tracing. Ex: set_extra_headers('t0', {'X-Custom':'v', 'Authorization':'Bearer ...'}) → {"set":["X-Custom","Authorization"]} |
| set_viewportB | Change viewport size mid-session. mobile=True triggers touch events + UA. For mobile UI / breakpoint reveal. Ex: set_viewport('t0', 375, 667, mobile=True) → {"width":375,"height":667} |
| dragA | Humanized bezier-jitter mouse drag. Slider captchas, drag-drop UI, range sliders. Ex: drag('t0', 100, 200, 400, 200) → {"ok":true} |
| dynamic_hookA | Network rule (legacy thin wrapper around Ex: dynamic_hook('t0', '/api/items', 'fulfill', new_status=200, new_body='{"items":[]}') |
| route_addB | Install rich interception rule. action: block|fulfill|continue|modify|tee|redirect. MATCH (AND of any provided): url_pattern substring (cheap, default) url_regex re.fullmatch method GET/POST/... resource_type Document/XHR/Fetch/Script/Stylesheet/Image/Font/Media/... header_match {header_lower: regex} — re.search per header status_min/max response-stage filter (forces response-stage interception) |
| route_add_manyA | Bulk-install rules in one round-trip. Each item is a dict matching |
| route_block_setA | Toggle the engine's inherited blocking. Ex: route_block_set('t0', trackers=False) → {"trackers":false,"resource_types":[]} Ex: route_block_set('t0', resource_types=['Image','Font','Media']) Ex: route_block_set('t0') → {"trackers":true,"resource_types":[],"tracker_blocks":42} |
| route_set_enabledA | Pause or resume a rule without removing it. Preserves hits/captures. Ex: route_set_enabled('t0','r2',False) → {"id":"r2","enabled":false,"hits":12} |
| route_capturesA | Return per-rule capture buffer (filled when rule has Ex: route_captures('t0','r2') → {"_untrusted":true,"id":"r2","captures":[...]} |
| route_removeA | Remove one rule (rule_id) or all rules (all=True). Doesn't disable Fetch domain. Ex: route_remove('t0', 'r2') → {"removed":"r2","active_routes":3} Ex: route_remove('t0', all=True) → {"cleared":5,"active_routes":0} |
| route_listB | List active rules + per-rule hit counts. Ex: route_list('t0') → {"routes":[{"id":"r0","action":"block","hits":12,...}]} |
| har_record_startA | Begin buffering every paused req+resp into a HAR-1.2 archive (tab-scoped). Body capture is best-effort (skipped for fulfilled/blocked requests). Forces response-stage interception to capture status+body. Ex: har_record_start('t0') → {"recording":true,"existing_entries":0} |
| har_record_stopA | Stop recording (keeps buffered entries — call har_dump to retrieve, har_clear to drop). Ex: har_record_stop('t0') → {"recording":false,"entries":42} |
| har_dumpA | Return recorded HAR. With For full-fidelity inline dump, raise both caps OR write to disk. Disk write is byte-exact. Ex: har_dump('t0') → {"_untrusted":true,"har":{...},"entries":42} Ex: har_dump('t0', path='/tmp/session.har', clear=True) → {"wrote":"/tmp/session.har","bytes":98231,"entries":42,"cleared":true} Ex: har_dump('t0', max_str=50000) # don't truncate JSON bodies up to 50KB |
| har_replay_loadA | Load a HAR file (path) or raw JSON string (har_json) into the replay corpus.
Subsequent matching requests (method+url, or url-only when loose=True) are fulfilled
from the HAR entry instead of hitting the network. Replay is fallthrough — it only
fires on requests no Ex: har_replay_load('t0', path='/tmp/session.har') → {"loaded":42,"loose":false} Ex: har_replay_load('t0', har_json='{"log":{"entries":[...]}}', loose=True) → {"loaded":N,"loose":true,"total_loaded":N} |
| har_clearB | Drop buffered HAR entries (recording=True, default) and/or the replay corpus. Ex: har_clear('t0') → {"cleared_entries":42} Ex: har_clear('t0', recording=False, replay=True) → {"cleared_replay":120} |
| clear_logsB | Wipe console+network buffers. For clean post-action trace. Ex: clear_logs('t0') → {"cleared":true} |
| get_cookiesB | All cookies in tab context. For session debug, audit tracking. Ex: get_cookies('t0') → {"_untrusted":true,"cookies":[{"name":"...","value":"...","domain":"..."}]} |
| set_cookiesB | Set cookies (each: name/value/domain required). For ad-hoc auth injection. Ex: set_cookies('t0', [{'name':'sid','value':'abc','domain':'.x.com','secure':True}]) → {"set":1} |
| check_detectionA | Stealth self-test: sannysoft (~7s) + creepjs if deep (~40s). Before sensitive nav. Ex: check_detection() → {"sannysoft":{"passed":31,"failed":0,...},"verdict":"good"} Ex: check_detection(deep=True) → {...,"creepjs":{"detected_headless":0,"stealth":0},"verdict":"good"} |
| warm_sessionA | Pre-warm w/ plausible browsing (profile: general/shopping/news/minimal). Before bot-detection-heavy targets so they don't see 0-history → checkout. ~10-30s. Ex: warm_session('shopping') → {"warmed":true,"profile":"shopping"} |
| rotate_fingerprintA | Re-seed canvas/audio/WebGL noise mid-session. Needs stealth_mode='full' at spawn. Ex: rotate_fingerprint('t0') → {"new_seed":2937184832} |
| handoff_startA | Spin up live remote-view URL (192-bit auth token in path). For captcha/2FA/manual steps. tunnel=True (default) → public via cloudflared Quick Tunnel; falls back to 127.0.0.1. Use start/wait PAIR to announce URL before blocking. fps 3 default (8-12 local). Ex: handoff_start('t0', 'solve recaptcha') → {"url":"https://abc.trycloudflare.com/h-XYZ/","reason":"...","next":"tell user to open URL, then call handoff_wait"} |
| handoff_waitB | Block until user clicks I'M DONE (or timeout). Pair with handoff_start. Ex: handoff_wait('t0', 120) → {"completed":true,"current_url":"...","current_title":"..."} |
| request_user_inputA | One-shot handoff: spin up + block. URL is in return. Use only if you don't need to announce URL early; otherwise prefer handoff_start + handoff_wait pair. Ex: request_user_input('t0', 'solve captcha', timeout_s=300) → {"url":"...","completed":true,"current_url":"..."} |
| tls_fetchA | Raw HTTP w/ Chrome JA3+JA4. Skip DOM when you only need JSON/HTML. ~50ms vs ~500ms.
Skip on client-side-rendered SPAs. Needs Ex: tls_fetch('https://api.example.com/users') → {"status":200,"headers":{...},"body":"{"users":[...]}"} |
| session_saveA | Save cookies+localStorage encrypted (Fernet+PBKDF2-200k). Per-(domain,name). Run AFTER login on target tab. Reuse via session_load. Ex: session_save('t0', 'github-me', 'hunter2') → {"path":"~/.local/share/umbra/sessions/github.com/github-me.fern"} |
| session_loadA | Decrypt + inject saved cookies/localStorage. Skips login. Load BEFORE auth-walled nav. Ex: session_load('t0', 'github-me', 'hunter2') → {"name":"github-me","cookies":12,"localStorage_keys":4,"origin":"https://github.com"} |
| session_listA | List all saved session blobs (metadata, no passphrase needed). Ex: session_list() → {"sessions":[{"name":"github-me","domain":"github.com","saved_at":...,"size":1234}]} |
| session_deleteB | Delete a saved session blob by name. Ex: session_delete('github-me') → {"deleted":true} |
| set_verbosityA | Toggle response minification. 'compact'=default (drops nones, columnar, trunc). 'full'=raw bytes, no trunc. Flip 'full' for byte-exact ops, then back. Ex: set_verbosity('full') → {"prev":"compact","now":"full"} |
| batchC | Run N tools in one round-trip, serial. Saves MCP framing + composes w/ dedup. |
| cleanup_staleA | Manually reap tabs idle ≥ idle_seconds + browsers with no remaining tabs. Ex: cleanup_stale(idle_seconds=300) → {"closed_tabs":["t2"],"closed_browsers":[]} |
| cloak_statusA | Report CloakBrowser install state. No network. Returns {platform, supported, kill_switch, env_binary, cache_root, installed[]}.
|
| cloak_installA | Download + verify the latest (or pinned) CloakBrowser chromium build. Idempotent — already-cached installs return their path. Pass Ex: cloak_install() → {"ok":true,"path":"/home/u/.umbra/cloak/.../chrome","tag":"..."} |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 87 tools
Most tools have distinct purposes, but there are notable overlaps: aria_click vs click_at, aria_type vs paste_text vs fill_form, extract_text vs extract_markdown vs dom_query vs grep_text, dynamic_hook vs route_add, handoff_start+handoff_wait vs request_user_input, and close vs close_browser vs kill_all vs cleanup_stale. The descriptions usually state which tool to prefer, but an agent still has to internalize many nuanced precedence rules.
The surface is overwhelmingly snake_case with recognizable domain prefixes (proxy_pool_*, route_*, har_*, session_*, aria_*, extract_*, wait_for_*). Minor deviations exist, such as bare verb names (spawn, close, back, forward), noun-ish names (current_state, cloak_status), and the legacy dynamic_hook, but the convention remains readable and predictable.
With 87 tools, the server is far beyond a well-scoped set and includes many niche capabilities that could be consolidated or made optional. The count creates a large discovery and selection burden even though the individual tools are mostly meaningful.
The tool surface covers browser lifecycle, navigation, interaction, extraction, stealth/fingerprinting, proxy pools, request routing, HAR recording/replay, cookies/sessions, downloads, uploads, handoff, batching, and cleanup. For a browser-automation server, there are no obvious core gaps or dead ends.