Skip to main content
Glama

Secure Browser MCP

An MCP server that gives an AI client (Claude, etc.) controlled access to a real headless browser on your server — with domain allowlisting, SSRF protection, per-session isolation, an audit log, and cookies/state that survive restarts.

Why "secure" specifically

Browser-automation MCPs are risky by default because the LLM effectively gets a pair of hands on a live browser that can reach anywhere on the internet (and, if misconfigured, your internal network). This server closes the common holes:

Risk

Mitigation

SSRF (browser tricked into hitting internal services / cloud metadata endpoint)

src/security.ts resolves DNS itself and blocks private/loopback/link-local IP ranges, independent of what hostname was requested

DNS rebinding (domain allowlisted, but later resolves to an internal IP)

DNS is re-resolved and IP-checked on every navigation, not cached

javascript: / data: / file: URL abuse

Scheme is rejected before anything touches the browser

Unrestricted destinations

Hard allowlist via ALLOWED_DOMAINS — fails closed if empty

Unauthenticated access to the MCP endpoint

Bearer token required on every request (MCP_AUTH_TOKEN)

Session/cookie leakage across tasks

Each sessionId gets its own isolated BrowserContext (separate cookie jar, storage, cache)

Resource exhaustion

MAX_SESSIONS cap + idle-session reaper (closes contexts unused for 30 min)

Silent/undetectable misuse

Every tool call is written to a SQLite audit_log table with session, params, and result

Oversized responses blowing up context

Text and screenshot payloads are size-capped

Drive-by downloads

acceptDownloads: false by default

This covers the common attack surface, but you're still exposing a browser to an LLM. Keep ALLOWED_DOMAINS as narrow as your task allows, and run this on a host/container with no access to anything sensitive — treat it like you would a CI runner that executes untrusted code.

Related MCP server: mcp-browser-server

Persistent storage — what's actually persisted

Two things, both in SQLite at ./data/browser-mcp.db (path configurable via DATA_DIR):

  1. Browser state — cookies + localStorage per session, captured via Playwright's storageState() and restored on the next browser_navigate call for that sessionId. This is what lets a session stay logged in to a site across server restarts. Call browser_persist_session to save explicitly, or browser_close_session (which persists automatically).

  2. Audit log — every tool invocation, its params, and outcome, so you can review what the browser actually did later (browser_audit_log).

If you'd rather keep this in Supabase instead of local SQLite (e.g. so multiple server instances share state), swap storage.ts for Supabase calls — the function signatures are small and self-contained, so it's a drop-in replacement.

Setup

npm install
npx playwright install --with-deps chromium   # downloads the browser binary
cp .env.example .env
# edit .env: set MCP_AUTH_TOKEN and ALLOWED_DOMAINS
npm run build
npm start

For local iteration without building: npm run dev.

The server listens on POST http://localhost:8787/mcp (Streamable HTTP transport). Point your MCP client at that URL with:

Authorization: Bearer <your MCP_AUTH_TOKEN>

Tools exposed

  • browser_navigate(sessionId, url) — allowlist + SSRF-checked navigation

  • browser_get_text(sessionId, selector?) — read page/element text

  • browser_click(sessionId, selector)

  • browser_type(sessionId, selector, text)

  • browser_screenshot(sessionId) — base64 PNG

  • browser_persist_session(sessionId) — force-save cookies/localStorage

  • browser_close_session(sessionId) — persist + free browser resources

  • browser_list_sessions()

  • browser_audit_log(sessionId, limit?)

sessionId is any string you choose (e.g. "pranav-github-login") — reuse the same one to keep continuity (logged-in state, cookies) across calls.

Deploying on your existing server

  • Render: same pattern you used for the MongoDB MCP — set env vars in the dashboard (don't bake MCP_AUTH_TOKEN into the image), expose port 8787, and set the health check to GET /mcp returning 401 (expected, since it's unauthenticated) rather than a 200.

  • Put this behind HTTPS (Render/most PaaS do this for you) — the bearer token is meaningless over plain HTTP.

  • If the server also hosts other things, run this in its own container so the idle-session reaper and MAX_SESSIONS cap actually bound its resource use independently.

Extending

  • To let the LLM choose domains dynamically instead of a static allowlist, add an approval step (return a tool result asking for confirmation) rather than opening ALLOWED_DOMAINS wide.

  • To persist to Supabase instead of SQLite, replace the functions in src/storage.ts; the audit log schema maps directly to a Postgres table.

  • The MCP TypeScript SDK evolves — if npm install pulls a version with a different StreamableHTTPServerTransport API, check https://github.com/modelcontextprotocol/typescript-sdk for the current signature.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • A paid remote MCP for AI agent browser MCP session, built to return verdicts, receipts, usage logs,

  • A paid remote MCP for AI agent browser approval MCP, built to return verdicts, receipts, usage logs,

  • Driflyte MCP server which lets AI assistants query topic-specific knowledge from web and GitHub.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pranavgawasproject/secure-browser-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server