mcp-server-diwall-browser
Attaches to a Chromium-family browser via the Chrome DevTools Protocol to observe screenshots and the accessibility tree and to perform actions in the operator's authenticated browser session.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
mcp-server-diwall-browser
English | Français | Deutsch | Español
https://github.com/user-attachments/assets/2e7f579f-5466-4bb2-b78d-9c617ebdbfc9
An MCP server that gives a language model the eyes and hands of an operator in the operator's own browser, through the Chrome DevTools Protocol: the model sees the same screenshot and the same accessibility tree as the operator, and acts in the same authenticated session. It needs a Chromium-family browser that the operator starts with a dedicated profile and a debugging port bound to the loopback interface, and an encrypted directory (gocryptfs) for credentials, captures, downloads and the files the model may attach to a form.
Before the first use
The server does not work without an encrypted directory: the profile of the browser it attaches to keeps your
signed-in sessions, and they must not sit on the disk in clear. There is no mode without encryption. After
installing, the first command, which you run yourself in a terminal, is mcp-server-diwall-browser-setup: it
reads your machine and ends on the next command to type.
mcp-server-diwall-browser-setup create --temporarymakes a volume in memory, for a trial. Closing it, stopping the machine or ending your session destroys it, and with it the sessions of the sites you opened in the browser, the audit journal and the evidence.mcp-server-diwall-browser-setup createmakes a durable volume.mcp-server-diwall-browser-setup open,browserandcloseopen the volume, start the browser on its profile, and stop both.
You type the password yourself, at the prompt of gocryptfs; the assistant never reads it. You sign in to your sites
yourself, in the browser it starts. After adding the server to your MCP client, restart the client: a session
that was already open does not see it. The steps, the refusals and the manual way are in the section "First launch" of
docs/OPERATOR_GUIDE.md.
Related MCP server: @tokens/dsh-browser-use
Which sites the model reads
The list of sites in the operator profile bounds what the model reads and acts on, never where it navigates. The key
origin_policy chooses what the server does with an origin that the list does not hold:
strict(the default, also when the key is missing): the origin is refused, and only the origin is returned.discovery: the server asks you in a window, once per origin:Refuser(the default answer),Cette séanceorAjouter à la liste, which writes that exact origin in your profile.open: every public site is read and acted on, and its content goes to the provider of the model; local addresses and names stay on the list. It is the freest policy and the riskiest, and it is yours to choose.
Payments, irreversible deletions and secret changes keep their confirmation, and a credential is typed only on the exact origin it is bound to, in the three policies. The details are in section 5 of docs/OPERATOR_GUIDE.md.
Install
On Linux the server ships as native packages. Download the one of your system from the Releases page of the repository or from the install guide of the website, then install it from the folder that holds it:
System | Command |
Debian 13, Ubuntu 24.04 and 26.04 |
|
Fedora 44 |
|
openSUSE Leap 16.0 |
|
Arch Linux |
|
Installation, the start of the server by an ordinary account and removal are validated in containers on
these systems. A container shows neither the confirmation window, nor the mounting of the encrypted
directory, nor a browser. Debian 12 and Ubuntu 22.04 are not supported: their Python is older than 3.12.
The installation needs a network connection: the package creates a Python virtual environment and
installs the dependencies, at the versions the project tested. The manual page is man mcp-server-diwall-browser.
To install from a clone of the repository instead, follow docs/OPERATOR_GUIDE.md.
A 64-bit ARM machine takes the same .deb, since the package does not depend on the architecture: a Raspberry Pi 4 under Debian 13 was tried with the package of the first release (see "What is tested, what is not"). The browser there is the distribution's Chromium.
Connect it to an MCP client
The server speaks MCP over standard input and output. With a package installed, the command is
mcp-server-diwall-browser and the operator profile is read from its standard place:
claude mcp add --scope user diwall-browser -- mcp-server-diwall-browserFrom a clone of the repository, with Claude Code:
claude mcp add --scope user diwall-browser \
-e MCP_DIWALL_BROWSER_PROFILE="$HOME/.config/mcp-server-diwall-browser/profile.json" \
-- "$HOME/mcp-server-diwall-browser/.venv/bin/python" -m mcp_server_diwall_browserFor another MCP client, the generic mcpServers block. The client does not expand ~, so this
command prints the block with your home directory in the paths, ready to paste into its
configuration:
cat <<EOF
{
"mcpServers": {
"diwall-browser": {
"command": "$HOME/mcp-server-diwall-browser/.venv/bin/python",
"args": ["-m", "mcp_server_diwall_browser"],
"env": {
"MCP_DIWALL_BROWSER_PROFILE": "$HOME/.config/mcp-server-diwall-browser/profile.json"
}
}
}
}
EOFAdapt ~/mcp-server-diwall-browser to where you cloned the repository, and the profile
path to where you wrote your operator profile. The variable can be left out when the operator
profile is at its standard place, ~/.config/mcp-server-diwall-browser/profile.json. The form of
the claude mcp add command was checked against claude mcp add --help, then run by the operator
on Debian: claude mcp list showed the server connected. The tests start the server exactly as
these two blocks do:
python -m mcp_server_diwall_browser with MCP_DIWALL_BROWSER_PROFILE set.
Everything that comes before (installing, starting the browser, opening the encrypted directory, writing the operator profile) is in docs/OPERATOR_GUIDE.md. How it works and why is in docs/ARCHITECTURE.md.
What the model sees

The image above was produced by scripts/smoke_test.py from the local demo page
(docs/demo/index.html). The numbered marks are drawn by the server: an action names an element
by its mark. The password field, which the page filled itself, is masked by a solid rectangle, and
its value is withheld from the text the model receives (redacted_fields is 1).
What is tested, what is not
Tested:
The full suite, on Debian 13 with Python 3.12.11 and the dependencies of
requirements.lock: without a browser, 2276 tests pass, 1 is skipped (a test that runs as root only) and the 632 that need a browser are deselected; with Google Chrome 156.0.8078.79 (headless, blank temporary profile), 2908 tests pass and 1 is skipped, with no expected failure.Download tests: two failed at random, 7 and 8 times in 30 runs of each alone, because the server closed the download setting before the browser had chosen what to do with the download. After the change they failed 3 and 1 times in 130 runs of each alone; the 3 were a read, in the test itself, of a file the browser renames, which is now one helper that ignores the rename. Since then each of the two alone failed 0 times in 200 runs, twice over. A third test, which closed the setting at once and expected a late download to be refused, failed 4 times in 200 alone: the real browser can choose before the server closes the setting, so the test is replaced by tests on a simulated browser. The measures and what remains are in
docs/JOURNAL.md.scripts/smoke_test.pyreturns 0 against Google Chrome 155.0.8059.84 and 3 when no browser is found.A Raspberry Pi 4 Model B with 8 GB, Raspberry Pi OS on Debian 13 (aarch64), Python 3.13.5 and Chromium 155.0.8059.79: the
.debinstalled byapt; the first launch of the setup assistant end to end with the realgocryptfs(temporary and durable volumes, browser, close);scripts/smoke_test.pyreturning 0; the server driven through a real MCP client on a visible Chromium with a window, not headless, on a virtual X11 screen, with the native confirmation window ofzenityopened and left to expire; then the package removed and purged, no file left. The complete suite there gave 2694 tests passed and 1 skipped in 15 minutes (five of them are measurements of that machine that the repository does not hold). On that network, the first page loaded by a freshly started Chromium took 15 to 26 seconds, which made three tests fail in a first run; with the browser cut off from the network, they all pass (guide, section 12).The marks and the masks at a screen scale of 1, 1.5 and 2, on a headless Chromium 141: the box of every mark in view lies within two pixels of the box the browser itself gives, every pixel of the password field of the demo page is under its mask, and the mask goes no further than two pixels beyond it (
tests/test_scale_integration.py). Before the correction of this, a mask did not cover its field at a scale above 1. A real screen at 1.5 was not measured.The page zoom, at 100, 125, 150 and 200 % and at screen scales of 1 and 1.5, on a headless Chromium 141 whose blank profile holds the zoom as its default: the image the model receives is the whole view, the password field of the demo page is under its mask, and so is the field of a page scrolled so that it sits near the bottom or in the middle of the view; the mask goes no further than two pixels beyond the field, and in the 16 placements measured no edge of a mask lay inside its field (
tests/test_zoom_integration.py,tests/test_zoom_scrolled_integration.py). Before the correction, at 125, 150 and 200 %, 0 % of the field was covered andcapturereadok. No image is published (capturereadsunavailable, the tree is still served) when the browser gives no usable zoom, when the zoom changes during the capture, or when the image is more than two pixels from the size of the view on either axis (tests/test_perception_zoom.py, which needs no browser). On Chrome 154 the operator measured the size of the image cut as the browser reads it (the view at each zoom, a pixel short in height at 125 %); the zoom tests were replayed on it later (below).A click of the server lands on its target at page zooms of 100, 125, 150 and 200 % and screen scales of 1 and 1.5, on a headless Chromium 141 whose blank profile holds the zoom as its default. The click is the server's own (
Actions.click, with its gate and its limits); the test page counts what each of its buttons receives and a separate session reads the counts. At each of the eight combinations a button of 120 by 32 CSS pixels, a button of 16 by 16 between two decoys 4 pixels from it, a button two and a half views down, and the large button after the page was scrolled to put it in the middle of the view, each received exactly one click, no decoy received any and none fell outside a button. On a page scrolled in both directions the click lands on the button put in the middle of the view, and a button covered by a banner fixed to the view is refused (element_obscured) with nothing clicked (tests/test_click_integration.py, 264 tests;tests/test_click_targets_page.py, 51, andtests/test_click_lookup_point.py, 20, need no browser). The same check refuses a click sent to the point of the box multiplied by the zoom, divided by it, or multiplied by the screen scale (64 of 64 such clicks reached no button), and a server that asks the browser about the point of the view. Before the correction, 20 of the 32 attempts at the first page failed, those that needed a scroll, and a button covered by the banner was accepted and the banner was clicked, at the eight combinations: the browser answers the node lookup for a point of the document, and the click gave it a point of the view. The measures are indocs/JOURNAL.md.A click on a page that moves when the pointer first moves (a menu that opens on hover, a banner that slides in). The test page shifts each target 60 CSS pixels down at the first mouse movement and puts an intruder button where the target was. At each of the eight combinations of page zoom and screen scale, for each of three buttons (a large one, a 16 by 16 one between two decoys, one two and a half views down), a click of the server either lands once on the target or is refused as covered (
element_obscured) with nothing clicked, and in the 24 attempts it is refused every time (tests/test_click_shifting_integration.py, 80 tests;tests/test_click_shifting_page.py, 30, andtests/test_click_moving_page.py, 10, need no browser). Before the change the same 24 attempts returned no error and the click landed on the intruder. After a refusal, a click on a new observation lands. The server now asks the browser again what lies under the point after the movement and before the press, every time, so a target that is covered where it stands, by a button shown at the first movement, is refused too (tests/test_click_covering_integration.py, 3 tests, on Chrome 154.0.8037.97). The 24 attempts above were measured on Chromium 141.0.7390.37. The measures, and those of a page that scrolls without stopping, are indocs/JOURNAL.md.A dense page with a consent banner fixed over its lower part (643 things to click): its three buttons are marked and listed first in the tree, the elements under it say
covered, and a click on "Refuse" by its mark takes the banner away (tests/test_ranking_integration.py).The setup assistant,
mcp-server-diwall-browser-setup: 50 tests on a simulated machine (mount tables, package managers, no/dev/fuse, no terminal, a LUKS volume); then, with the real gocryptfs 2.5.1 in a pseudo-terminal, on throw-away volumes:create --temporary, the server readingmounted,browser(Google Chrome 155.0.8059.84 without a screen, attached,profile_encrypted: true),close(no volume left in memory, no file of the browser profile on the disk), thencreate,closeandopenof a durable volume; and in a clean Debian 13 container, wherecheckgavesudo apt install gocryptfs fuse3, which succeeded.The command that carries an old credential file over (see the operator guide): on files of the test suite in a simulated volume, with a refusal tested for each way out, and no value in anything it prints. It was not tried on a real gocryptfs volume.
A real gocryptfs 2.5.1 volume, a throw-away one made for the check (not the operator's own), mounted on Debian:
statusreadmountedandprofile_encrypted: true, and the server wrote its journal and createddownloads/in it. Opera 136 (Chromium 152), started with its profile in that volume and a debugging port on the loopback interface, was attached; the server opened a page of a public shopping site, filled its search field, pressed Enter and observed the results. The typed text does not appear in the journal.The origin policy (
strict,discovery,open):tests/test_origin_policy.pyandtests/test_origin_policy_server.pyon a simulated browser, andtests/test_origin_policy_integration.py, 12 tests of the server started by its entry point against Google Chrome 155.0.8059.84, with fake window programs: a portal that links to a second origin, a redirection to an unknown origin, the three answers of the window (the profile read back afterAjouter à la liste), local names refused underopen, a payment that still asks its window, and the policy changed in the profile without restarting. The windows of the realkdialogandzenitywere shown in a virtual screen and answered by keystrokes (Enter and Escape give the refusal); a person answered them (the next entry).The confirmation window, shown on the operator's workstation and answered by a person, with the operator's own gocryptfs volume mounted and a local payment page, in Opera 138.0.6044.24, once with
kdialog25.04.0 and once withzenity4.1.90 (the server found only that tool): with each, a refusal returnedconfirmation_deniedand the page received nothing; an acceptance made the click and the page received one request; with no answer, the request expired after 90.4 seconds (confirmation_expired) and the page received nothing. The journal holds onerequestedline and one line with the outcome for each of the six requests.Claude Code, started from a desktop terminal:
statusthrough Claude Code readconfirmation_channel: native, so the display reached the server.The server process over standard input and output: it writes nothing but the protocol on its standard output, holds no listening socket and runs with a core file limit of zero.
Not tested:
A 64-bit ARM machine other than that Raspberry Pi 4 (a Raspberry Pi 5, another distribution), a Wayland desktop session, and a person answering the confirmation window there.
The setup assistant on a machine that has a swap area (its report is tested on a simulated list), and its
browsercommand with Brave, Chromium or Opera.Brave, and Google Chrome builds other than the one the operator ran.
The confirmation asked through a desktop MCP client (elicitation): tested with the client of the MCP SDK only.
Chrome 154 with its profile kept inside the operator's own encrypted directory.
The page zoom made by the Ctrl + key of a browser that is not headless. The protocol has no command for a zoom, so the tests write the default zoom of a blank profile; whether the browser keeps the two kinds in the same way is not confirmed. A screen whose scale really is 1.5 was not measured either; the click tests have the same two limits.
The click tests on Chrome 154. The correction reads a property of the browser's node lookup measured on Chromium 141 (it answers for a point of the document, not of the view): if it differs on another version, the tests of a click on a scrolled page fail there. A click on an element inside a frame of a scrolled page was not measured.
A control that acts on the press rather than on the click, put under the pointer by a script at the instant of the press: the press is checked after it is made, so the page's own handlers of the press have run. The guard of a key and of a text is a script of the page: a page that expects it goes round it, and it does not see the events of a frame (Google Chrome 156.0.8078.47,
docs/OPERATOR_GUIDE.md, section 7).
Known limits:
The encrypted directory protects as much as its password does: whoever has the password reads its content, and a weak or reused password protects little.
The safeguards are those measured on the date of this version, by the tests and measurements this document names. A model more recent than the ones tried can find a way around them that no trial has seen.
While the server is attached to the browser, downloads are refused in every tab, the operator's own tabs included, except in the short window around one of the server's clicks or Enter keys. To download a file yourself, stop the server or the MCP client; downloads work again as soon as the server's connection ends.
A download that a page starts more than half a second after a load ends is refused.
Accepting a
confirmor apromptdialog asks the operator in a window, whatever its message says. Accepting a dialog whose message asks for a payment, a deletion or a secret change is always refused: no capture can be taken while a dialog is open. The operator answers such a dialog.A secret that a site writes in the path of an address is shown as written in the results; only the value of a query parameter with a name such as
token, and the fragment, are redacted.Visibility is approximate: the server does not compute contrast or
overflowclipping, so text a human cannot read can still reach the model.An observation holds at most 150 marks and 400 tree lines (
marks_truncated,tree_truncated). Past that, the server keeps first what is in a layer above the page (a dialog, a modal node, a fixed or sticky node painting over content in view, such as a cookie banner), listed under anoverlayline in the tree, then what is in view and clear, then what is in view under a layer (covered: true, and a click on it is refused), then the rest. A banner that overlaps nothing in view is not a layer and can still be cut.The content of frames is never read; frames are masked in the image.
What the model provider keeps of what it receives is not confirmed by this project.
Credits
mcp-server-diwall-browser is built in an asymmetric human–LLM collaboration. The roles are written down as they were actually played; the full list is in CONTRIBUTORS.md.
Ronan Davalan: architect and arbiter. Vision, security requirements, decisions, validation and testing.
Claude Code (Anthropic; Opus 5.5, Sonnet 5.5 and Haiku 5.5): systems engineer and lead developer. Server implementation, tests, documentation, the text and upkeep of the project's website, the scene that illustrates the first release and its short clip, rendered in Blender on the workstation, driven through LibreModular-MCP, and a field report of the first launch from the Debian package.
Claude Cloud (Anthropic): wrote part of the code on branches, from prompts, reviewed before every merge.
Muse Spark 1.3 Contributor (Meta, through OpenCode): first use of the server by a model other than Claude, on a local instance of a web application, whose field report shaped the next fixes; the stylesheet and layout of the project's website, chosen among several trials, the text and the behaviour of the pages left untouched; two readings of the website, with wording proposals in four languages kept as drafts and defects found, which were corrected.
DeepSeek V4 Pro (DeepSeek, through OpenCode): independent security audit of the first release, from a blank session; four of its seven findings were confirmed at triage and corrected.
GLM 5.3 (Zhipu, through OpenCode): independent security audit of the same tag, from a blank session, with reproductions on a headless browser; three of its seven findings were confirmed at triage and corrected.
DeepSeek V4.1 Flash 1017 (DeepSeek, through OpenCode): the 3D emblem "the glass D", drawn in Blender on the workstation, in two pupil variants (a round pupil and a pointer); it is the eye of the scene that illustrates the first release.
Gemini (Google): architectural advisor and synthesizer; gave the project its name, Diwall, "dike" in Breton.
DeepSeek (DeepSeek, through an online chat): advice on separating the accounts; three of its ideas are in the isolation levels: a command the user runs to check the separation, a guide that shows the accounts and what each one can read, and an inventory of the side doors (debugging port, screen,
sudo, mount,/proc).gemini-3.5-flash (Google, through OpenCode): tested the connection through a dedicated agent: the tools reach that agent only, and its shell asks before running.
deepseek-v4.1-flash-free (DeepSeek, through OpenCode): used the server from an account of its own, with no administration and no access to the screen: it signed in on a local page with credentials the server typed, and left a test payment to the confirmation window, which expired.
Space Bunny Free (opencode): the project's logo, the round-eyed D, used on the website, its icons and the title page of the manuals; pruning of files at the start of the project.
License: MIT, see LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Let AI agents use your real, signed-in Chrome: tabs, clicks, forms, screenshots and video.
Connect AI agents to your own Chrome with scoped access, approvals, and verified task results.
Let your AI agent operate any website through a real, logged-in browser session.
Give AI agents a real cloud Chrome: open sessions, navigate, read pages, click, type and screenshot.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to securely control a user's existing signed-in Chrome browser through isolated tab groups, with strict per-session ownership and no cookie or token exposure.6 npmISC
- AlicenseNot gradedqualityBmaintenanceEnables language models to control a real visible browser with a full set of 24 tools for navigation, clicking, form filling, screenshots, and reading page structure, while preserving login states and supporting configurable browser policies.171 npmMIT
- AlicenseBqualityBmaintenanceEnables an AI agent to drive a real Chrome instance on a hidden Windows desktop, acting on accessibility element references instead of coordinates, with each session isolated on its own private desktop, profile, and ports. Credentials are typed into a masked popup and injected by an origin-bound, DPAPI-encrypted broker, so passwords never reach the model.27MIT
- AlicenseNot gradedqualityAmaintenanceEnables coding agents to inspect and operate web pages in the user's Chrome browser through a single browser tool, carrying out clicks, typing, navigation, and page reads while pausing for approval on risky or sensitive actions.5 npm1MIT