Shade Browser Security MCP
Standalone MCP server exposing only the security-classified tools from Shade Browser, enabling Brave CDP browser automation across 20 isolated profile slots. Built on FastMCP and Playwright Python.
README
Shade Browser Security MCP
This repository exposes only the explicitly classified SECURITY tools from
Shade Browser through a standalone MCP server. The implementation reuses the
shared shade_browser_mcp browser core vendored in this repository; the
public shade-browser-security entry point registers a separate FastMCP
instance with the SECURITY allow-list only.
Run locally:
pip install -e '.[dev]'
shade-browser-security
The reduced/general server is maintained separately in the
shade-browser-mcp repository's reduced-safe branch.
MCP server for Brave CDP browser automation across 20 flock-protected profile slots. Built on FastMCP + Playwright Python.
What this is
A one-shot-installable Model Context Protocol server that gives Claude Code a Brave browser with real cookies, auto-launched on demand, across 20 isolated slots (ports 61000-61019). Each slot gets its own profile copy by default, so parallel agents never collide on tabs or sessions. Persistent mode reuses a stable per-slot profile for manual logins, extensions, and browser config.
Install (3 commands)
uv tool install git+https://github.com/Atlas-X-AI/shade-browser-mcp
shade-browser-mcp postinstall
claude mcp add shade-browser-mcp --scope user -- shade-browser-mcp
Then restart Claude Code. Tools appear as mcp__browser-automation__*.
What each step does
| Step | What it does |
|---|---|
uv tool install git+... |
Installs the Python package globally (isolated venv managed by uv) |
shade-browser-mcp postinstall |
Runs playwright install chromium — one-time Playwright browser fetch |
claude mcp add ... --scope user ... |
Registers the MCP in ~/.claude.json (user scope = available in all projects) |
Upgrade an existing user-scope MCP install
Claude Code user-scope MCP registration runs the uv tool install copy of
shade-browser-mcp. New tools do not appear in already-installed Hayden agent
sessions until that tool install is replaced and Claude Code reconnects to the
server.
For local development on the checked-out branch:
git switch feat/form-filler-bolt-on
uv tool install --force --editable .
Or install this branch directly from Git:
uv tool install --force git+https://github.com/Atlas-X-AI/shade-browser-mcp@feat/form-filler-bolt-on
Then fully exit and relaunch each Claude Code session that should see the new schema. Existing sessions keep the old MCP process and old tool list until they restart or reconnect that MCP server.
Smoke-test the installed server's tool registry:
uvx --from fastmcp fastmcp list --command "shade-browser-mcp" --json \
| grep -E '"name": "form_(scan|fill|gate_check)"'
Prerequisites
- Python 3.10+
- uv —
curl -LsSf https://astral.sh/uv/install.sh | sh - Claude Code CLI (for
claude mcp add) - Brave browser at
/opt/brave-bin/braveor/usr/bin/brave-browser - Brave profile at
~/.config/BraveSoftware/Brave-Browser(that's where your cookies come from — each slot gets a freshcp -acopy)
Run shade-browser-mcp doctor after install to verify all six prerequisites.
Commands
shade-browser-mcp # runs FastMCP stdio server (default — what Claude Code invokes)
shade-browser-mcp serve # same as above
shade-browser-mcp postinstall # playwright install chromium
shade-browser-mcp doctor # health check: Brave, profile, Playwright, Claude registration
shade-browser-mcp --version
Runtime identity and safe rolling reload
Call the machine-readable MCP tool below on every connected server and after connecting to a candidate build:
browser_runtime_status(
client_id="launcher-a",
expected_version="0.1.10",
expected_revision="<candidate-revision>"
)
The response includes the running server's version, revision, pid,
UTC started_at, transport, effective focus defaults, and top-level
reload_required/should_reload booleans. A build mismatch is deterministic:
status is reload_required, reload.action is reconnect, and
reload.process_action is always none. The server does not kill or restart
itself and does not raise Brave, move the pointer, type, or call
bring_to_front for this protocol. Focus defaults are
browser_navigate=false and browser_switch_tab=false.
For a rolling reconnect, the launcher/client owns the transport lifecycle:
- Start the candidate server alongside the current server, injecting an
immutable build value such as
SHADE_BUILD_REVISION=$(git rev-parse --short HEAD). - Probe the candidate with
browser_runtime_statuswithout expected values and record its returned version/revision as the target identity. - Ask one client at a time to report status against that target identity. A
client on the old server receives
reload_required=true; close only that client's transport and reconnect it to the candidate. Other clients remain usable on the old server until their own turn. - Verify each reconnected client with the same expected version/revision. A
matching response has
status=ready,reload_required=false, and the new PID/start time. Only the launcher decides when the old server is retired; this tool never performs that action.
If expected_version/expected_revision is omitted, the tool reports the
identity but cannot detect staleness. If the revision is unknown, configure
SHADE_BUILD_REVISION in the launcher; do not infer freshness from PID or
start time. A reconnect failure leaves the existing client transport alone and
must be handled by the launcher/client's normal retry policy.
Form-Filler Tools
Three bolt-on tools scan and fill ordinary web forms from a runtime JSON profile path:
form_scan(slot=-1)
form_fill(slot=-1, profile="docs/examples/form-fill-demo-profile.json", allow_submit=false)
form_gate_check(slot=-1, profile="docs/examples/form-fill-demo-profile.json")
form_scan returns field IDs, labels, required signals, option lists, upload
controls, submit/advance controls, and profile-key hints such as
person.email. form_fill reads the profile at call time, fills grounded
fields, verifies read-back, and returns counts plus a gate check. Its response
also includes additive field_outcomes entries with tier_used, committed,
readback_value, escalate, and reason for each resolved field. Outcomes may
also include attribution, for example pre_filled_verified:radio, when an
already-filled control was verified before any actuation. form_gate_check
verifies required fields, identity read-back, required uploads, visible page
errors, and detector health.
Profile shape:
{
"person": {"email": "casey@example.test", "first_name": "Casey"},
"company": {"name": "Example Labs"},
"files": {"Resume": "relative/or/absolute/path.txt"},
"values": {"Work Type": "full time", "Location": ["Remote United States", "Remote"]},
"never_submit": true
}
values entries override inferred person/company mappings by field ID or
label. Option choices are grounded only by exact text/value equality after
mechanical normalization, or by explicit profile variant lists. Relative upload
paths resolve relative to the profile file. If never_submit is true,
form_fill(..., allow_submit=true) still refuses to click submit.
Worked examples after navigating a browser slot to a form:
form_scan(slot=4)
Returns form inventory such as field IDs, labels, required signals, upload controls, options, submit controls, and inferred profile-key hints.
form_fill(slot=4, profile="docs/examples/form-fill-demo-profile.json", allow_submit=false)
Fills grounded fields from the JSON profile, verifies read-back, uploads files
declared in files, and returns a nested gate check without submitting.
form_gate_check(slot=4, profile="docs/examples/form-fill-demo-profile.json")
Reports whether required fields, identity fields, required uploads, visible validation errors, and detector health are all ready for a guarded submit.
See docs/examples/form-fill-demo-profile.json and
docs/examples/form-fill-live-slot-transcript.md for a real-slot loopback
fixture transcript. docs/examples/form-fill-local-transcript.md is a shorter
local fixture summary. The consolidated capabilities, schema, safety, packaging,
and opt-in upgrade guide is docs/form-fill/README.md.
Headed vs headless mode
By default, shade-browser-mcp launches Brave headed if DISPLAY or WAYLAND_DISPLAY is set, and headless otherwise (CI, SSH sessions without X forwarding, systemd units without a user display).
To force a visible window regardless of env:
export HEADED=1
shade-browser-mcp
To force headless on a desktop session:
unset DISPLAY WAYLAND_DISPLAY
shade-browser-mcp
The HEADED=1 knob is useful when you want to watch an AI agent drive the browser, or when the MCP daemon was launched from an env that lacks DISPLAY but you later want to attach visibly.
Persistent profiles and Bitwarden
Default slot mode refreshes ~/.cache/brave-cdp/slot-N/ from the real Brave profile on launch. Persistent mode uses ~/.cache/brave-cdp/persistent/slot-N/ and reuses that profile across launches, so manual ChatGPT login, installed extensions, and browser settings survive.
export SHADE_PROFILE_MODE=persistent
export SHADE_INGEST_BW=1
shade-browser-mcp
You can also launch one slot manually:
launch-brave-cdp 5 --persistent
SHADE_INGEST_BW=1 runs ~/bin/bw-ensure.sh when BW_SESSION is absent and passes the resulting BW_SESSION only through the MCP/Brave launcher environment. Tokens are not written to manifests, screenshots, or browser session files.
Architecture (one-liner)
BrowserManager._ensure_brave_alive() on each tool call → httpx-probe port → if dead, subprocess.run(bash launch-brave-cdp N) with env including DISPLAY/WAYLAND_DISPLAY/XAUTHORITY/XDG_RUNTIME_DIR → poll 20s at 1Hz → playwright.connect_over_cdp(). Slots are flock-protected via cdp_slots.claim_slot().
Per-slot CDP auth proxy (Phase 1, library only)
A protocol-layer slot-ownership proxy ships in shade_browser_mcp.cdp_proxy. Each slot now carries an auth_token (256-bit, generated by claim_slot) and a private_port (62000+slot) where Brave will eventually bind. The proxy listens on the public port (61000+slot), validates Authorization via a /t/<token>/... URL prefix, and rewrites webSocketDebuggerUrl so Playwright's WS upgrade also carries the token.
CLI:
python -m shade_browser_mcp.cdp_proxy <slot> # passthrough mode
python -m shade_browser_mcp.cdp_proxy <slot> --require-token # enforce
Phase 1 ships the library + tests + audit fixes only — the proxy is not yet wired into _ensure_brave_alive. That production wiring (and the matching launch-brave-cdp --private-port flag) lands in a Phase 2 follow-up. Users who want to opt in early can drive the proxy module directly. See DONE.md for the migration ledger and audit-deferred items.
Shade Fleet Console (local dashboard)
A read-mostly local web console for watching and steering the 20 slots. It is a single-host operator tool, not a shared service.
export SHADE_DASHBOARD_TOKEN=$(openssl rand -hex 32)
shade-browser-mcp dashboard --port 8790
- Loopback only. The console binds
127.0.0.1exclusively and refuses to start on any non-local host (no--allow-nonlocalescape hatch in v1). - Token via env. The bearer token is read from the
SHADE_DASHBOARD_TOKENenvironment variable. - Opener URL carries the token in the fragment, never a query string. On
start the server prints
http://127.0.0.1:8790/#token=<token>. The fragment (#token=...) is never sent to the server in a request line and never lands in access logs; a query string (?token=...) would, so it is deliberately not used. - API auth uses a header. Every
/api/*request must carryAuthorization: Bearer <token>. The page JS reads the token from the URL fragment and replays it as that header; it is never written tolocalStorage/sessionStorage.
Phase 2 endpoint model
Phase 2 splits each slot into a private upstream port and a public proxied port:
| Port | Who binds it | Reachable by | Auth |
|---|---|---|---|
127.0.0.1:6200N (private) |
Brave (CDP) | proxy only | n/a (loopback, private) |
127.0.0.1:6100N (public) |
per-slot auth proxy | local clients | tokenized path |
- Brave now binds the private per-slot port
127.0.0.1:6200N. - The per-slot auth proxy listens on the public port
127.0.0.1:6100N. - Internal Playwright attach uses the tokenized path
http://127.0.0.1:6100N/t/<token>— the token rides in the URL path so it survives the WebSocket upgrade. - Direct unauthenticated access to
6100Nis rejected with401. A missing or wrong token never reaches Brave.
Synthetic Action View
The console renders each slot's recent activity from structured data, not from a live pixel feed:
- Structured browser events are written to
browser-events.jsonl(a separate stream from the existingactions.jsonlaudit log), surfaced throughGET /api/slots/{n}/events. - Each slot shows its current URL/title plus compact ARIA/text snapshots.
- Screenshots are captured only on demand, on failure, or as evidence — there are no continuous thumbnails and no rolling viewport capture.
POST controls (all under Authorization: Bearer <token>):
POST /api/slots/{n}/navigate # navigate the slot to a URL
POST /api/slots/{n}/screenshot # capture one frame on demand / as evidence
POST /api/slots/{n}/release # release the slot
POST /api/slots/{n}/focus # focus / observe the slot
POST /api/slots/{n}/takeover # operator takeover
POST /api/slots/{n}/resume # hand control back to the agent
Example read call (sanitized fleet view):
SHADE_TOKEN="$SHADE_DASHBOARD_TOKEN"
curl -H "Authorization: Bearer $SHADE_TOKEN" http://127.0.0.1:8790/api/fleet
The single-frame capture endpoint is POST /api/slots/{n}/screenshot; issue it
only when you actually need an on-demand frame.
v1 non-goals
The console deliberately does not include:
- an agent-task launcher from the web view,
- continuous screenshots or live viewport streaming,
- an injected in-page recorder,
- cloud sync, multi-user accounts, TLS, or any public (non-loopback) binding.
Uninstall
claude mcp remove shade-browser-mcp --scope user
uv tool uninstall shade-browser-mcp
rm -rf ~/.cache/brave-cdp/ # purge slot profile copies
Development
git clone https://github.com/Atlas-X-AI/shade-browser-mcp
cd shade-browser-mcp
uv sync --dev
uv run shade-browser-mcp doctor
Design notes
- Vendored dependencies —
cdp_slots.pyandlaunch-brave-cdpship inside the package (loaded viaimportlib.resources). No symlinks, no external~/bin/scripts, no assumptions about the host. - Wheel loses exec bit — the shell script is invoked via
bash <path> <slot>regardless of its executable bit after install. - No Brave auto-install — intentional. The package won't try to install Brave for you; run
shade-browser-mcp doctorto find out if you're missing it. - User-scope MCP registration — the server runs under
uv tool install's isolated venv;claude mcp add --scope userpoints Claude at the installed entry-point, no manual JSON surgery.
Supersedes
/home/anombyte/Hermes/current-projects/atlas-shade/shade-mcp/browser-server.py(monorepo path)/home/anombyte/bin/browser-mcp-proxy.sh(wrapper script)/home/anombyte/bin/launch-brave-cdp(now vendored)
Previous wiring relied on an external ~/bin/launch-brave-cdp and a symlinked cdp_slots.py from claude-launcher. That's fragile across reinstalls — this package fixes it.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。