savage_mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@savage_mcpOpen https://github.com/dominikduda/savage_scraper and summarize what it does"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
savage_mcp
Savage MCP is a least-privilege browser-reading MCP for Savage Scraper. It lets an MCP client open and scrape explicitly allowlisted pages through the Chrome profile you already use.
It uses standard local MCP stdio transport and can be launched by any MCP host that supports local stdio servers.
Why this exists
Many browser MCPs are automation tools: they can click, type, submit forms, execute scripts and operate applications. That is useful when you want an agent to control a browser.
Savage MCP is for a different job:
Let the AI read selected websites from your existing logged-in Chrome session without also giving it a general browser-control API.
Your real browser and your existing session
Savage Scraper runs in your normal Chrome profile. When Savage MCP opens an allowlisted site, that page uses the browser session you already have, including existing authentication and application state.
There is no separate headless browser to maintain, no automation profile whose cookies need to be kept in sync, and no separate machine just to reproduce access you already have in Chrome.
Chrome remains a normal human browser at the same time. Savage MCP owns one dedicated agent tab for its operations; your other tabs remain yours, and Savage Scraper restores the previously active tab when possible.
A small API on purpose
The MCP tool surface is intentionally limited:
savage_open(url)- validate the URL againstallowed_hostsand optionalallowed_paths, open or reuse the dedicated Chrome tab, perform the bounded lazy-load pass and return simplified HTML.savage_scrape()- re-scrape the existing dedicated Savage MCP tab.savage_status()- report bridge/config/extension status.
There are intentionally no tools for:
clicking elements;
typing into pages;
submitting forms;
arbitrary JavaScript execution; or
unrestricted browser control.
That is a feature, not an unfinished automation API. If you only need the model to read Jira, GitHub, documentation, dashboards, internal tools or other authenticated sites, giving it fewer browser capabilities reduces the accidental write/action surface.
It is not a guarantee that opening a page can never have side effects; websites can implement their own behavior on page load. The design simply avoids exposing general mutation primitives to the MCP client.
If your task requires an agent to operate websites, complete workflows, fill forms or debug the browser, use a full browser-automation tool instead.
Explicit site boundary
allowed_hosts defines which HTTP/HTTPS hosts the MCP may open. Optional allowed_paths rules can narrow an allowed host to specific URL paths. The combined policy is enforced by both Savage MCP and Savage Scraper after the local bridge is authenticated.
This gives the setup a deliberately simple trust model:
existing Chrome session
+ explicit allowed_hosts
+ optional per-host allowed_paths
+ read-oriented MCP tools
= browser context for the modelRelated MCP server: WebControl
Architecture
MCP host
|
| MCP over stdio
v
Savage MCP
|
| authenticated WebSocket
| ws://127.0.0.1:<port>
v
Savage Scraper
Chrome extension
|
v
normal Chrome profileThe WebSocket server binds only to 127.0.0.1. A shared bridge token is used for mutual authentication between Savage MCP and the extension. Ordinary web pages are rejected by the bridge's Chrome-extension origin check.
Multiple MCP processes
MCP hosts may start more than one Savage MCP process at the same time-for example, separate OpenCode sessions or concurrent local jobs. Savage MCP processes that resolve to the same configuration file automatically share one browser bridge.
One process is elected as the bridge leader. It owns the configured browser WebSocket port and the authenticated Savage Scraper connection. Additional Savage MCP processes act as proxies over local OS IPC and forward browser/status requests to that leader. Requests from all participating processes are serialized by the leader before they reach Chrome, so multiple MCP clients do not compete for the single dedicated browser tab.
If the leader exits, a proxy can take over leadership on a later request or status check. Savage Scraper's normal bridge reconnect behavior then connects to the new leader. savage_status reports the local process role (leader or proxy) and the active leaderPid alongside the browser connection state.
The coordination group is keyed by the resolved Savage MCP configuration path. Processes using the same ~/.config/savage_mcp/config.json (or the same explicit SAVAGE_MCP_CONFIG) share a bridge. Processes using different configuration files are independent and may therefore require different bridge ports.
Requirements
macOS, Linux, or another platform where Node.js and Chrome run on the same host
Node.js 20 or newer
Google Chrome
Savage Scraper with MCP support installed
Install
npm install
npm run initnpm run init creates:
~/.config/savage_mcp/config.jsonIf XDG_CONFIG_HOME is set, it is respected. You can also set SAVAGE_MCP_CONFIG to use an explicit configuration path.
The generated configuration contains a random bridge token and an empty whitelist:
{
"bridge_port": 8765,
"bridge_token": "<random 64-character token>",
"allowed_hosts": [],
"allowed_paths": {},
"close_after_scrape": false,
"agent_tab_close_seconds": 90
}Configure allowed hosts
Edit allowed_hosts with the domains the MCP is permitted to open and scrape. If a host has no allowed_paths entry, every path on that already-allowed host remains available:
{
"allowed_hosts": [
"jira.company.com",
"github.com",
"*.internal.example.com"
]
}An exact host permits every path on that host:
jira.company.compermits:
https://jira.company.com/
https://jira.company.com/browse/ABC-123
https://jira.company.com/anything/else?x=1It does not permit other.company.com.
A wildcard such as:
*.internal.example.compermits subdomains such as one.internal.example.com and deep.one.internal.example.com, but not the bare internal.example.com. Add both patterns if you need both.
Global wildcard patterns are deliberately rejected.
Optionally restrict paths
allowed_paths is an optional tightening layer keyed by the same normalized host patterns used in allowed_hosts:
{
"allowed_hosts": [
"github.com",
"*.github.com",
"jira.company.com"
],
"allowed_paths": {
"github.com": [
"/dominikduda/savage_scraper/**"
],
"*.github.com": []
}
}The rules are:
allowed_hostsis always the outer gate. A path rule can only narrow a host that is already allowed.If an allowed host has no matching
allowed_pathsentry, all paths on that host are allowed.An empty array such as
"*.github.com": []also means all paths on that matched host pattern are allowed.A non-empty array restricts that host pattern to the listed paths.
A plain path such as
/owner/repois an exact match.A path ending in
/**, such as/owner/repo/**, matches the base path and everything below it.Other
*wildcard placement is rejected. Path rules cannot contain a query string or fragment; matching uses the URL pathname only and is case-sensitive.Every
allowed_pathskey must also be present inallowed_hosts.
If more than one allowed host pattern matches a hostname, the most specific one controls the path policy: an exact hostname wins over a wildcard, and a longer wildcard suffix wins over a broader wildcard. This prevents a broad unrestricted rule from bypassing a narrower restriction.
For GitHub repository-only access, for example:
{
"allowed_hosts": ["github.com"],
"allowed_paths": {
"github.com": [
"/dominikduda/savage_scraper/**"
]
}
}This allows the repository root and its descendants while rejecting other github.com paths. As with allowed_hosts, *.github.com does not match the bare github.com; add both host patterns if you need both.
Agent-tab closing
close_after_scrape controls whether Savage Scraper closes the dedicated MCP tab immediately after a successful savage_open or savage_scrape operation. The result is captured before the tab is closed. The default is false, which preserves the reusable-tab behavior.
"close_after_scrape": falseagent_tab_close_seconds controls the inactivity fallback. When close_after_scrape is false, it closes the reusable tab after that idle period. When close_after_scrape is true, it still acts as a safety fallback if a scrape fails before the immediate-close path completes. The allowed range remains 30–3600 seconds; the default is 90.
Configure Savage Scraper
Install/update Savage Scraper.
Find Savage Scraper and open Extension options.
Under MCP integration, paste the
bridge_tokenfrom~/.config/savage_mcp/config.json.Set the same bridge port (default
8765).Click Enable MCP website access and approve Chrome's optional HTTP/HTTPS site-access prompt.
Save the settings.
The optional broad Chrome host permission makes autonomous operation possible. The actual operational restriction is the allowed_hosts list plus any configured allowed_paths rules in savage_mcp; the extension receives that policy only after mutual bridge authentication and enforces it again on the Chrome side.
Run directly
npm startThe process speaks MCP on stdout/stdin. Diagnostic logs go to stderr because stdout is reserved for the MCP protocol.
MCP host configuration
Configure your MCP host to launch Savage MCP as a local stdio process:
node /absolute/path/to/savage_mcp/src/index.jsThe process uses stdin/stdout for MCP. If your configuration file is somewhere other than the default location, set:
SAVAGE_MCP_CONFIG=/absolute/path/to/config.jsonUse your MCP host's normal local-server configuration to supply that command and optional environment variable.
OpenCode example
For OpenCode, add a local MCP entry like this to opencode.json or opencode.jsonc:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"savage": {
"type": "local",
"command": [
"node",
"/absolute/path/to/savage_mcp/src/index.js"
],
"enabled": true
}
},
"experimental": {
"mcp_timeout": 300000
}
}If savage_mcp uses a non-default config path, add an environment object to the savage MCP entry with SAVAGE_MCP_CONFIG pointing to that file. The larger MCP timeout is useful for queued or slow browser reads.
Multiple OpenCode processes can use this configuration simultaneously. OpenCode may launch separate local Savage MCP processes for different sessions or workspaces; those processes coordinate internally through the shared bridge described above. A singleton OpenCode server or custom attach wrapper is not required for Savage MCP multi-session use.
Config reload behavior
allowed_hosts, allowed_paths, close_after_scrape and agent_tab_close_seconds are re-read and synchronized before tool requests, so changes do not require source-code changes and normally do not require restarting the MCP process.
Changing bridge_port or bridge_token requires updating Savage Scraper's MCP options too. After changing the port, restart the currently running Savage MCP host processes so a newly elected leader binds the new port. A token change also requires the extension to reconnect with the matching token.
Browser behavior
Savage Scraper owns exactly one dedicated MCP tab:
savage_opencreates it if necessary.Later
savage_opencalls navigate/reuse the same tab.Concurrent
savage_open/savage_scrapecalls-including calls from multiple Savage MCP processes sharing the same config-are serialized by the shared bridge leader before they reach Chrome.Savage Scraper also serializes browser operations on the extension side as a second correctness boundary around the shared tab.
The user's previously active Chrome tab is restored after the operation when possible.
By default (
close_after_scrape: false), the MCP tab closes after the configured inactivity timeout.With
close_after_scrape: true, the MCP tab closes immediately after each successful scrape; a latersavage_opencreates it again as needed.Only HTTP/HTTPS URLs permitted by
allowed_hostsand any matching non-emptyallowed_pathsrule can be opened or scraped.
Before each MCP scrape, Savage Scraper performs a bounded lazy-load pass on the main page scroll only using larger downward steps, then jumps directly back to the original position before extraction. The extension pins scripting work to Chrome's current main-document ID; transient main-document replacement is retried up to 3 times within a 60-second operation/retry budget. This is intended to handle ordinary redirects/reloads/document swaps without allowing infinite retries, and it is not a universal virtualized-content crawler.
Security notes
The bridge listens only on
127.0.0.1.Browser WebSocket connections must originate from a Chrome extension.
A shared bridge token is used for mutual HMAC authentication; the token itself is never sent over the socket.
allowed_hostsand optionalallowed_pathsrestrictions are enforced by both the MCP process and Savage Scraper.savage_mcpnever exposes arbitrary page JavaScript execution.Treat the bridge token like a local secret. The generated config file is created with user-only permissions where supported.
Scraped page data is returned to the MCP host. What the MCP host/model provider does with that data depends on your MCP host and model-provider configuration.
Development
Syntax checks:
npm run checkHost-whitelist tests:
npm testYou can also test a local MCP server with the MCP Inspector after installing dependencies:
npx @modelcontextprotocol/inspector node src/index.jsLicense
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Stealth web browser for agents: search, fetch, click, download and type in persistent MCP sessions.
Hosted real Google Chrome MCP with per-user persistent state. Navigate, click, type, screenshot.
Screenshot, PDF, OG-image, and page extraction (markdown/JSON) over MCP. Bearer key or x402.
MCP server (stdio): fetch web pages as clean readable markdown via the AgentForge API
Related MCP Servers
- AlicenseAqualityAmaintenanceFetches any URL and returns clean markdown, using a real Chrome fingerprint to bypass bot detection. Integrates as an MCP server with tools like fetch_markdown.158 npm3MIT
- FlicenseNot gradedqualityCmaintenanceHeadless browser automation for LLM agents via REST API or MCP tools. Enables navigating pages, reading structured content, clicking elements, filling forms, and executing JavaScript.-
- FlicenseNot gradedqualityDmaintenanceEnables MCP-compatible clients to scrape and extract data from live websites using a headless Chromium browser, with support for CSS selectors, link extraction, table extraction, and Amazon product pages.-
- AlicenseNot gradedqualityBmaintenanceAn MCP server that gives AI clients a Steel-managed Chromium browser to read pages that block plain fetch, take screenshots, and interact with sites via clicking, typing, and form filling, with explicit browser sessions and access to the accessibility tree or markdown.MIT