Computer and browser control
malac-control is a separate binary that exposes the desktop (screenshots, mouse, keyboard) and Chromium-based browsers as MCP tools. malac starts it automatically when it is installed, so the agent can click through a web app, read a page that needs JavaScript or a login, or operate a desktop application.
Because it is a plain MCP server, other agents can use it too, and it can run on another machine: see Controlling a remote machine to test GUIs and websites on a remote desktop.
How malac connects
Section titled “How malac connects”malac connects to the control service like any other MCP server, trying in this order:
[control] url, if set (and only that).- A running service announced in
~/.malac/control.json, whichmalac-control servewrites. - A spawned
malac-control stdio, found next to themalacbinary or onPATH(or[control] command).
The daemon connects the control service once when it starts and shares it between all tabs; a run in its own process (-p, --json) connects its own. The terminal UI’s View menu and the web UI’s home page show whether it is connected and, on macOS, whether it has its permissions.
Its tools are added without a server prefix: computer_screenshot, browser_click and so on. Tools that only read (screenshots, snapshots, listings, waiting) are marked read-only and never need approval; the others follow your permission mode.
To run without it: malac --no-control, or:
[control]enabled = falseRun it as a service
Section titled “Run it as a service”malac-control serve --port 7878 # writes ~/.malac/control.json; malac connects to itmalac-control profiles # list browser profilesserve listens on 127.0.0.1:7878 by default and requires a token. Without --token (or MALAC_CONTROL_TOKEN), it generates a random one and writes the URL, token and process id to ~/.malac/control.json, readable only by you. malac reads that file and connects; the file is removed when the service stops. Clients send the token as a bearer token or a ?token= query parameter.
A long-lived service is useful on macOS: start it once from a terminal that has the privacy permissions, and the daemon uses it; see Make it stick.
malac control <args> runs malac-control with the same arguments.
To use a service on another machine, set [control] url and token_env; Controlling a remote machine walks through it, including an SSH tunnel for the unencrypted WebSocket.
Browser profiles
Section titled “Browser profiles”Each browser profile is a separate Chrome instance with its own user data directory under ~/.malac/browser/<name>, so logins persist between sessions and never mix. A profile called default always exists. Configure more in ~/.malac/control.toml:
[browser.profiles.work]
[browser.profiles.personal]clone_from = "Profile 1" # copy an existing Chrome profile on first launch
[browser.profiles.debug]cdp_url = "http://127.0.0.1:9222" # attach to a browser you started with remote debugging- A managed profile (no
cdp_url) is launched by malac-control with its own data directory. Log in once inside it, and the session cookies stay there. clone_fromseeds a managed profile from a system Chrome profile (by directory name, display name or path) on its first launch. Caches are skipped.cdp_urlattaches to a browser that is already running with--remote-debugging-port.browser_closeonly disconnects from it.
Every browser tool takes an optional profile argument (default default), so you can say “use the work profile”. malac-control profiles lists configured profiles and the system browser profiles that can be cloned.
When the service stops, it closes the browsers it launched. Attached browsers keep running.
control.toml
Section titled “control.toml”~/.malac/control.toml (or $MALAC_HOME/control.toml, or --config <path>). Every key is optional.
| Key | Type | Default | Meaning |
|---|---|---|---|
computer.enabled |
bool | true |
Offer the computer_* tools |
computer.max_screenshot_width |
integer | 1366 |
Wider screenshots are scaled down. At least 320. |
browser.enabled |
bool | true |
Offer the browser_* tools |
browser.executable |
path | detected | Chrome, Chromium, Brave or Edge binary |
browser.headless |
bool | false |
Run managed browsers without a window |
browser.profiles.<name>.clone_from |
string | none | System profile to copy on first launch |
browser.profiles.<name>.cdp_url |
string | none | DevTools endpoint to attach to. Cannot be combined with clone_from. |
Profile names may contain letters, digits, - and _. The web UI’s Control service settings edit this file too.
Desktop control
Section titled “Desktop control”The computer_* tools take a screenshot first; every x/y argument is a pixel position in the latest screenshot of that display, so the agent clicks what it saw. See Control service tools for the full list.
macOS permissions
Section titled “macOS permissions”On macOS, desktop control needs Screen Recording (Screen & System Audio Recording on macOS 15 and later) and Accessibility. Browser control needs neither. Without Screen Recording, the tools report the missing permission instead of returning a screenshot of the wallpaper.
Check and request
Section titled “Check and request”# What is granted, and to which appmalac-control permissions
# Show the macOS prompts and open System Settings at what is missingmalac-control permissions --request
# The same report as JSONmalac-control permissions --jsonScreen Recording: grantedAccessibility: missingGranted to: iTerm (/Applications/iTerm.app)followed by a hint on what to do next. The same report is available to the agent as the computer_permissions tool (read-only; with request, it shows the system prompts), to the terminal UI as View → Request macOS permissions and the control needs permissions status segment, and to the web UI on its home page.
Which app gets the permission
Section titled “Which app gets the permission”macOS grants these permissions to the responsible app of a process tree: the app the tree started from. It is fixed when the tree starts and survives the daemon detaching itself. So when the daemon starts malac-control:
- a daemon started from iTerm (by running
malacthere) runsmalac-controlas iTerm’s, - a daemon started by the VS Code extension runs it as VS Code’s,
- a process with no app above it, for example started over SSH, gets the permission as the
malac-controlbinary itself.
malac-control permissions names that app under Granted to. Grant both permissions to it in System Settings → Privacy & Security (add it with + if it is not listed), then quit and reopen that app and restart the daemon: malac daemon restart.
Make it stick
Section titled “Make it stick”Because the daemon can be started by different apps, the grant can seem to come and go. To make it independent of who starts the daemon, run the control service yourself from a terminal that has both permissions:
malac-control serve # in Terminal.app or iTerm, with both permissions grantedIt writes ~/.malac/control.json, and the daemon connects to it instead of starting its own malac-control (restart the daemon if it already runs). Keep that terminal open while you work.
Models without vision
Section titled “Models without vision”Screenshots are images. For models that cannot read images, mark them vision = false and set a vision_model; malac replaces each image with a description written by that model before the request is sent:
vision_model = "lora/vision"
[models."lora/default"]vision = falseThe tutorial Automating a browser sets up a profile, logs in once and runs a scraping task.
malac is created by lobirus and released under the MIT license. About malac