Automation with MCP

mob3 -mcp runs MOB³ headlessly as a Model Context Protocol server over stdin/stdout. It exposes the browser to scripts and AI agents through a small set of text-oriented tools.

mob3 -mcp
mob3 -mcp -width 100 about:home
mob3 -mcp -width 120 https://en.wikipedia.org/

Client configuration

MOB³ speaks MCP over stdio, so any MCP client that can start a local command can use it. The examples below assume mob3 is installed at /usr/local/bin/mob3; replace that path with the output of command -v mob3 on your machine (for example /opt/homebrew/bin/mob3 on Apple Silicon).

All clients accept the same server arguments. -width controls wrapping and coordinates, and the optional starting URL defaults to about:home:

/usr/local/bin/mob3 -mcp -width 100 about:home

OpenCode

Add MOB³ as a global local server:

opencode mcp add mob3 --global -- /usr/local/bin/mob3 -mcp -width 100 about:home
opencode mcp list

Or add it by hand to ~/.config/opencode/opencode.json (use opencode.json in a project for project-local access):

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "servers": {
      "mob3": {
        "type": "local",
        "command": ["/usr/local/bin/mob3", "-mcp", "-width", "100", "about:home"]
      }
    }
  }
}

OpenCode reloads configuration automatically; opencode mcp list shows the connection state.

Claude Code

Add MOB³ at user scope so it is available in every project:

claude mcp add --transport stdio --scope user mob3 -- \
  /usr/local/bin/mob3 -mcp -width 100 about:home
claude mcp list

For a project-scoped server, put the equivalent entry in .mcp.json at the project root:

{
  "mcpServers": {
    "mob3": {
      "command": "/usr/local/bin/mob3",
      "args": ["-mcp", "-width", "100", "about:home"]
    }
  }
}

Cursor

Create .cursor/mcp.json in the project, or ~/.cursor/mcp.json for a global server:

{
  "mcpServers": {
    "mob3": {
      "command": "/usr/local/bin/mob3",
      "args": ["-mcp", "-width", "100", "about:home"]
    }
  }
}

Reload the MCP servers from Cursor's settings after editing the file.

Claude Desktop

Add the server to Claude Desktop's configuration file (claude_desktop_config.json):

{
  "mcpServers": {
    "mob3": {
      "command": "/usr/local/bin/mob3",
      "args": ["-mcp", "-width", "100", "about:home"]
    }
  }
}

Restart Claude Desktop after changing the file.

Other clients

Clients that use the common mcpServers shape can use the same entry shown for Cursor. The only required fields are command and args; the server needs no environment variables, URL or API key.

MCP communication is stdio-only. Stdout carries protocol messages only; diagnostics go to stderr. Do not expose the process as a network service; there is no authentication.

Protocol

Messages are newline-delimited JSON-RPC 2.0.

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}
{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"navigate","arguments":{"url":"https://example.com/"}}}

Supported methods:

Tool failures are returned as successful JSON-RPC responses whose content explains the error and whose isError field is true. Protocol errors use standard JSON-RPC error codes.

You can test the server without an MCP client:

printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"navigate","arguments":{"url":"https://example.com/"}}}' \
  | ./bin/mob3 -mcp

Debugging in the running browser

The interactive browser's ! console has a Tools tab for read-only inspection of the page you are looking at. Its commands mirror the inspection tools below: help, form [selector], links [selector], requests [limit], logs [limit], source [selector], snapshot, history and clear. Results scroll in the tab and keep the newest 500 lines, help lists the arguments and selector examples, and the scrollback survives page loads. clear (or cls) empties the Tools scrollback; the JS logs input accepts the same command to clear the JavaScript log. A [selector] is an optional CSS selector such as #content, .card, form#search, input[name=q] or main a; omit it to inspect all forms/links or the full page source.

Navigation, form editing (fill/select/toggle/submit), wait/wait_for and screenshot are available only through mob3 -mcp: the interactive event loop owns navigation results, so those commands cannot run synchronously from inside it.

Tools

Tool Arguments Returns
navigate {url} Snapshot after loading
snapshot {} Current page viewport, links and form controls
links {selector?} Every link with index, text, href and selector
screenshot {scale} PNG image of the current page viewport
click {link} or {text} or {selector} or {x,y} Snapshot after the click
form {selector?} Every form and control with CSS selectors and values
fill {selector,value} or {fields} filled N field(s)
select {selector,value} or {values} or {text} or {index} selected …
toggle {selector,checked?} … is checked / … is unchecked
submit {selector?} Snapshot after navigation
wait {ms} waited <ms>ms
wait_for {selector?} and/or {text?}, {timeout_ms?} found … after <ms>
type {text} typed
press {key} pressed <key>
scroll {lines} or {pages} or {top} or {bottom} Scroll percentage
back {} Snapshot
forward {} Snapshot
reload {} Snapshot
evaluate {expression} JavaScript result as text
history {} Session history, most recent first
requests {limit?} Current page's request log and cache usage
logs {limit?} JavaScript console messages and errors
source {selector?} Raw decoded HTML, or one element's serialized HTML

navigate

Resolves input like the URL prompt: URLs, local paths, about: pages, or a search query through -search. Waits for the page, meta refreshes and scripted redirects.

snapshot

Returns the current page viewport as plain text:

URL: https://example.com/
Title: Example Domain
Viewport: 100x27  Scroll: 0%

Example Domain
...

Links:
[1] More information... -> https://iana.org/domains/example

Inputs:
- input name="q" type="search" value="" at (4,12)

links

{}
{"selector":"main a"}

Lists every rendered anchor in the document, including links outside the current viewport, with the same [n] index that snapshot shows and click accepts:

Links: 2
[1] Docs -> https://example.com/docs selector="a[href=\"/docs\"]"
[2] Sign in -> https://example.com/login selector="#nav a:nth-child(2)"

Empty anchors, hidden anchors and anchors with no rendered content are omitted. selector filters which anchors are listed (it matches the anchor, not its containers). Listings are capped at 200 entries.

form

{}
{"selector":"form#search"}

Lists every form and its controls, including fields that are outside the current viewport. Each control shows its tag, name, type, current value or checked state, flags (required, readonly, disabled) and a stable CSS selector for the other form tools:

Forms: 1
[1] form method="GET" action="/find" selector="#search"
  - input name="q" type="text" value="old" selector="#search input[name=\"q\"]"
  - select name="lang" value="fr" options=["en" "fr"] selector="select[name=\"lang\"]"
  - input name="safe" type="checkbox" state=checked value="on" selector="#search input[name=\"safe\"]"

Hidden inputs are omitted because they cannot be edited. Controls outside any form are listed separately. Selectors prefer an #id, then a form-id name pair, then an nth-child path. Pass a form or control selector to list just that form.

fill

{"selector":"#search input[name=q]","value":"terminal browsers"}
{"fields":{"#search input[name=q]":"terminal browsers","textarea[name=notes]":"a note"}}

fill sets text-like inputs and textareas, replacing any existing value; an empty string clears the field. It accepts one selector/value pair or a fields map of selector to value. Values honor maxlength, and range inputs accept a number. Checkboxes, radios and selects are rejected: use toggle and select. fill never submits and never requires focus, so it also works on off-screen fields. Each change fires input and change.

select

{"selector":"select[name=lang]","value":"fr"}
{"selector":"select[name=tags]","values":["a","b"]}
{"selector":"select[name=lang]","text":"French"}
{"selector":"select[name=lang]","index":1}

Chooses by option value, visible label, or zero-based option index. Pass values for a <select multiple> to replace the selected set. A missing value or an out-of-range index is a tool error that lists the available options.

toggle

{"selector":"input[name=remember]"}
{"selector":"input[name=remember]","checked":true}
{"selector":"option[value=b]","checked":true}

Checks or unchecks a checkbox or radio. Omit checked to flip the current state. Selecting a radio unchecks the other radios in its group, including groups outside a form. Multi-select options can also be toggled this way. Use select to choose a single-select option or replace a whole multiple selection.

submit

{}
{"selector":"form#search"}

Submits a form: runs the supported constraint validation, fires the form's submit handler, builds the GET query or urlencoded POST body, navigates, and returns a snapshot. Without a selector it uses the focused control's form, or the page's only form. Validation failures, invalid actions and preventDefault() are returned as tool errors (validation also focuses the first invalid field), so the page does not change on failure.

wait and wait_for

{"ms":500}
{"selector":"#results","timeout_ms":5000}
{"text":"No results found","timeout_ms":2000}
{"selector":".item","text":"price","timeout_ms":5000}

JavaScript timers do not run on their own between tool calls. wait advances the page for a number of milliseconds (default 100, maximum 10000), running due timers, applying the DOM rebuilds they cause and waiting for navigations they start.

wait_for polls until a CSS selector matches an element and/or the given text appears in the rendered page. Text matching is case-insensitive, ignores markup and scripts, and sees the laid-out document rather than just the viewport. It returns found … after <ms> or fails with a timeout error; the default timeout is 5000ms and the maximum is 30000ms. When both selector and text are supplied, both must match.

Both tools return a short confirmation rather than a snapshot; call snapshot afterwards when you need the page contents.

screenshot

{"scale":1}

Returns an MCP image/png content block of the current page viewport, plus a short text line with the URL. scale 1 uses 8×16 pixel cells; scale 2 doubles the image. This is a terminal-cell raster, not a browser pixel screenshot: Go Mono glyphs are used, CJK/emoji glyphs may be missing, and MCP disables image downloads so image regions show alt text.

click

{"link":1}
{"text":"Sign in"}
{"selector":"input[name=q]"}
{"x":4,"y":12}

type and press

type requires a focused input, textarea or the URL prompt. Click a field first. A newline presses Enter; a tab presses Tab.

press accepts Enter, Tab, Escape, Backspace, Delete, arrows, Home, End, PageUp, PageDown and Space, plus single characters.

evaluate

Runs JavaScript in the page realm through goja:

{"expression":"document.title"}
{"expression":"JSON.stringify([...document.querySelectorAll('h2')].map(h => h.textContent))"}

Objects stringify as [object Object]; use JSON.stringify for structured results. -no-js disables evaluation.

requests, logs and source

{"limit":50}
{"limit":20}
{"selector":"main article"}

These expose the same diagnostics as the in-browser ! console:

history

Lists session history, most recent first; * marks the current entry.

Typical agent loop

  1. navigate a page.
  2. snapshot to read text and collect visible link numbers; use links when the target link is below the fold.
  3. form to get every control's selector and current value.
  4. fill, select and toggle to set fields without focusing or scrolling.
  5. submit to send the form, then read the returned snapshot.
  6. Use wait_for after clicks and submissions that load content with timers, then snapshot when ready.
  7. Use click/type/press for special widgets, evaluate for DOM extraction, or scroll/back/history to move.
  8. When a page loads wrong, check requests for blocked or missing resources, logs for script errors, and source for the markup that was fetched.

Limitations

See Security and privacy for the broader security model.