by Perzian
A local browser for agents that returns compact numbered text, not screenshots.
Pick Mantis if your agent spends too many tokens on screenshots or accessibility trees for read, navigate and form-fill work on public sites. The author reports 29 to 47 percent fewer input tokens on long tasks. Skip it for canvas apps, or if you want a hosted browser with proxies.
Pages arrive as short numbered text, so an agent clicks by #number without screenshots.
The edge is a compact page view tuned to cut tokens, which a rival could copy, and there is no hosted service or network effect behind it.
Main trade-off
A v0.1 beta benchmarked only on read, navigate and form-fill tasks with two models.
MIT licence. Free to install from npm and run on your own machine. The repository lists no paid tier and no hosted service.
npx -y -p mantis-browser mantis install && claude mcp add mantis -- npx -y -p mantis-browser mantis-mcp
The permissions this connector asks for, as its own documentation states them.
20 actions an agent can call. Open one for its parameters and an example.
Example call
{"tool":"open","arguments":{"url":"https://example.com"}}Parameters
| Parameter | Type | Needed | Description |
|---|---|---|---|
| url | string | Required | The address to open. |
Returns
| Field | Type | Description |
|---|---|---|
| observation | string | The page after the action, as numbered text; elements are addressed by #number. |
Example call
{"tool":"read","arguments":{"region":"nav"}}Parameters
| Parameter | Type | Needed | Description |
|---|---|---|---|
| region | string | Optional | A label substring such as "nav"; if none matches, text inside a region also works. |
| full | boolean | Optional | Untrimmed bounded excerpt of the page; use offset to get the next excerpt. |
| offset | number | Optional | Character offset for the next full excerpt. |
| fresh | boolean | Optional | Expand regions already seen, for this call only. |
Returns
| Field | Type | Description |
|---|---|---|
| observation | string | The page after the action, as numbered text; elements are addressed by #number. |
Example call
{"tool":"find","arguments":{"text":["Add to cart","Checkout"]}}Parameters
| Parameter | Type | Needed | Description |
|---|---|---|---|
| text | string or array | Required | The text to search for, or a list of texts to search in one call. |
| context_chars | number | Optional | Local context to return around each match, from 80 to 800 characters. |
Returns
| Field | Type | Description |
|---|---|---|
| observation | string | The page after the action, as numbered text; elements are addressed by #number. |
Example call
{"tool":"forget","arguments":{}}Returns
| Field | Type | Description |
|---|---|---|
| observation | string | A confirmation that region memory was cleared. |
Example call
{"tool":"click","arguments":{"id":12}}Parameters
| Parameter | Type | Needed | Description |
|---|---|---|---|
| id | number | Required | The #number of the element, from the last read. |
Returns
| Field | Type | Description |
|---|---|---|
| observation | string | The page after the action, as numbered text; elements are addressed by #number. |
Example call
{"tool":"type","arguments":{"id":7,"text":"mechanical keyboard","submit":true}}Parameters
| Parameter | Type | Needed | Description |
|---|---|---|---|
| id | number | Required | Which input to fill. |
| text | string | Required | The text to enter, replacing what is there. |
| submit | booleandefault false | Optional | Press Enter after typing. |
Returns
| Field | Type | Description |
|---|---|---|
| observation | string | The page after the action, as numbered text; elements are addressed by #number. |
Example call
{"tool":"select","arguments":{"id":9,"option":"Large"}}Parameters
| Parameter | Type | Needed | Description |
|---|---|---|---|
| id | number | Required | Which select or combobox to change. |
| option | string or array | Required | The option text; a multiple select takes a list and its selection becomes exactly that list. |
Returns
| Field | Type | Description |
|---|---|---|
| observation | string | The page after the action, as numbered text; elements are addressed by #number. |
Example call
{"tool":"fill","arguments":{"fields":[{"id":3,"value":"Ada Lovelace"},{"id":4,"value":"ada@example.com"},{"id":5,"value":true}]}}Parameters
| Parameter | Type | Needed | Description |
|---|---|---|---|
| fields | array | Required | A list of {id, value} objects; value is text, an option, a list of options, or true/false. |
Returns
| Field | Type | Description |
|---|---|---|
| observation | string | The page after the action, as numbered text; elements are addressed by #number. |
Example call
{"tool":"press","arguments":{"key":"Enter"}}Parameters
| Parameter | Type | Needed | Description |
|---|---|---|---|
| key | string | Required | The key to press, for example Enter or End. |
Returns
| Field | Type | Description |
|---|---|---|
| observation | string | The page after the action, as numbered text; elements are addressed by #number. |
Example call
{"tool":"scroll","arguments":{"direction":"down"}}Parameters
| Parameter | Type | Needed | Description |
|---|---|---|---|
| direction | stringdefault down | Optional | One of up, down, left or right. |
| id | number | Optional | The #number of an element: alone it scrolls into view, with a direction it scrolls that element's container. |
Returns
| Field | Type | Description |
|---|---|---|
| observation | string | The page after the action, as numbered text; elements are addressed by #number. |
Example call
{"tool":"back","arguments":{}}Returns
| Field | Type | Description |
|---|---|---|
| observation | string | The page after the action, as numbered text; elements are addressed by #number. |
Example call
{"tool":"wait_for","arguments":{"state":"appears","text":"Order confirmed","timeout_seconds":10}}Parameters
| Parameter | Type | Needed | Description |
|---|---|---|---|
| text | string | Optional | Text to wait for. |
| id | number | Optional | The #number of an element to wait for. |
| state | string | Optional | One of appears, disappears, present or enabled (the last two apply to an #id). |
| timeout_seconds | number | Optional | How long to wait, at most 10. |
Returns
| Field | Type | Description |
|---|---|---|
| observation | string | The page after the action, as numbered text; elements are addressed by #number. |
Example call
{"tool":"hover","arguments":{"id":15}}Parameters
| Parameter | Type | Needed | Description |
|---|---|---|---|
| id | number | Required | Which element to hover over. |
Returns
| Field | Type | Description |
|---|---|---|
| observation | string | The page after the action, as numbered text; elements are addressed by #number. |
Example call
{"tool":"upload","arguments":{"id":20,"path":"C:/Users/me/report.pdf"}}Parameters
| Parameter | Type | Needed | Description |
|---|---|---|---|
| id | number | Required | Which file input receives the files. |
| path | string | Optional | One local file path. Give exactly one of path, paths or files. |
| paths | array | Optional | A list of local file paths. |
| files | array | Optional | A list of local file paths (alternative spelling of paths). |
Returns
| Field | Type | Description |
|---|---|---|
| observation | string | The page after the action, as numbered text; elements are addressed by #number. |
Example call
{"tool":"tabs","arguments":{"action":"switch","id":2}}Parameters
| Parameter | Type | Needed | Description |
|---|---|---|---|
| action | string | Optional | One of list, switch or close. |
| id | number | Optional | The tab #number for switch or close. |
Returns
| Field | Type | Description |
|---|---|---|
| observation | string | The page after the action, as numbered text; elements are addressed by #number. |
Example call
{"tool":"media","arguments":{}}Returns
| Field | Type | Description |
|---|---|---|
| observation | string | A numbered list of images, canvases and charts. |
Example call
{"tool":"data","arguments":{"id":4}}Parameters
| Parameter | Type | Needed | Description |
|---|---|---|---|
| id | number | Optional | The item number shown by read or media. |
| selector | string | Optional | A CSS selector or #N reference to use instead of id. |
Returns
| Field | Type | Description |
|---|---|---|
| observation | string | The extracted text or values. |
Example call
{"tool":"ocr","arguments":{"id":5}}Parameters
| Parameter | Type | Needed | Description |
|---|---|---|---|
| id | number | Optional | Which image or canvas to read. |
| selector | string | Optional | CSS selector for the image or canvas. |
Returns
| Field | Type | Description |
|---|---|---|
| observation | string | The text recognised in the image. |
Example call
{"tool":"look","arguments":{"id":6}}Parameters
| Parameter | Type | Needed | Description |
|---|---|---|---|
| id | number | Optional | Which element to capture, by number. |
| selector | string | Optional | CSS selector of the element, used when there is no id. |
Returns
| Field | Type | Description |
|---|---|---|
| image | image/png | A PNG of the element, at most 800 px. |
| text | string | Text that accompanies the image. |
Example call
{"tool":"ask_human","arguments":{"reason":"Please solve the CAPTCHA and sign in","timeout_seconds":300}}Parameters
| Parameter | Type | Needed | Description |
|---|---|---|---|
| reason | string | Required | What the person must do. |
| until | stringdefault URL or content change | Optional | When to stop waiting: url_change, selector_gone:<css> or selector_present:<css>. |
| timeout_seconds | numberdefault 300 | Optional | How long to wait. |
Returns
| Field | Type | Description |
|---|---|---|
| observation | string | The page after the action, as numbered text; elements are addressed by #number. |
Mantis is an open-source MCP server from Perzian that lets AI agents browse the web as short numbered text instead of screenshots. It runs locally on Node 20 or newer with Chromium, needs no account, and works with Claude Code, Codex, Cursor or any MCP client. Version 0.1.14 shipped on 8 November 2026.
Pages as numbered text
The agent reads a compact numbered view of each page and acts by #number, with no screenshot step.
A fallback ladder for visuals
media, data, ocr and look step from text-only extraction up to a PNG capped at 800 px, used only when read is not enough.
Whole-form fill
fill takes a list of id and value pairs and sets text, dropdown and checkbox fields in one call.
Attach to your own Chrome
The mantis chrome command opens Chrome on its own profile with a local debugging port, and Mantis then drives that browser.
Human handoff
ask_human opens a headed window for CAPTCHA, 2FA, payment or login and waits 300 seconds by default; Mantis does not solve CAPTCHAs.
Site memory that stores no page text
Regions already seen are shortened on later reads; the memory holds hashes, counts and timestamps, expires after 30 days and is off with MANTIS_MEMORY=0.
Cut the calls on a multi-page task
In the selected README example the agent needed 11 to 12 tool calls with Mantis against 21 to 29 with the accessibility tree over 5 runs, as the author reports it.
Fill a signup or checkout form
fill takes every field in one call and, if the page re-renders half way, stops and reports what already landed, so the agent does not retype fields.
Keep an agent signed in to internal tools
Sign in once in the profile that mantis chrome opens and the sign-ins persist there; anything logged in there is also open to the agent, so use a profile you are willing to expose.
Read the numbers in a chart
data returns Chart.js, ECharts or Highcharts values as text; ocr and look are for when that finds nothing, and look returns an 800 px PNG at most.
Pause for a person on a CAPTCHA or payment
ask_human pauses the run until the page changes, but it needs a display, so it is not available on a headless server.
| Skill | Works with | Connects by | Checked |
|---|---|---|---|
| Claude · Codex · Cursor | MCP | 11 Nov | |
| Claude · Cursor · Windsurf · Gemini +1 | MCP | 16 Aug | |
| Claude · Cursor · Windsurf | MCP | 11 Oct | |
| Claude · Cursor · Windsurf · VS Code | MCP | 11 Oct |
The maker charges nothing: Mantis is MIT-licensed, published on npm, and the README and setup guide list no paid tier or hosted service (read 11 November 2026). Your own costs are the model tokens your agent uses and the machine that runs Chromium.
No. The README and setup guide list no signup or key; you install it with npx and the server runs on your machine. The only downloads are Chromium and English OCR language data during mantis install, and a failed OCR download is only a warning. For offline OCR, point MANTIS_TESSDATA at a folder holding eng.traineddata.
Yes. The mantis chrome command starts Chrome on 127.0.0.1:9222 with a dedicated profile, and since 0.1.14 Mantis attaches on its own while that Chrome is running. Sign in once in that profile and the sign-ins persist. The maker warns that the agent then controls those real tabs and every logged-in session in the profile; set MANTIS_AUTO_ATTACH=0 to always launch a fresh browser.
It is the author's claim, backed by a benchmark in the repository that bench/README.md explains how to re-run. With claude-haiku-5-5 on the long tasks, 40 of 42 runs were correct for both approaches, at a median 268k input tokens for Mantis against 506k for the tree. The README discloses that a few tasks were swapped after results, and no independent test is published.
Not by itself. SECURITY.md says Mantis does not sanitise page text, so prompt injection is possible, and the one-time note it sends the MCP client that tool output is untrusted is a hint to the model, not a guarantee. The upload tool reads any existing local file path, so upload only files you mean to disclose. Vulnerabilities go through a private GitHub security advisory.
Mantis is an open-source browser for AI agents, made by Perzian (the package lists Aadit Singal as author). It is a skill, not an app: you register it in an MCP client such as Claude Code, Cursor or Codex, and the agent gets tools to open pages, click, type and fill forms. Instead of screenshots or a full accessibility tree, each page comes back as short numbered text, and the agent acts by number. The repository was created on 14 October 2026 and the current version is 0.1.14.
It runs over stdio as a Node 20+ process that drives Chromium through Playwright, with no account or API key. The tools cover navigation (open, read, find, back, tabs), actions (click, type, select, fill, press, scroll, hover, upload, wait_for), a fallback ladder for images and charts (media, data, ocr, look) and ask_human for CAPTCHA, login and payment steps. An optional attach mode drives your own Chrome on 127.0.0.1:9222. On 11 November 2026 Hokai started the published 0.1.14 package as an MCP server and it listed 20 tools under the same names as the Actions below; no browsing task was run.
The maker's README measures Mantis against Playwright MCP's accessibility-tree view on public sites, using two models, gpt-6-luna and claude-haiku-5-5. On 14 keyed long tasks, the Luna runs answered 35 of 42 correctly with Mantis and 34 of 42 with the tree, at a median of 244k against 345k input tokens. These are the author's figures from a benchmark the repository lets you re-run. The README also says the screenshot baseline is the project's own tool and that a few tasks were swapped after results.
Look elsewhere if you need canvas, WebGL, maps or drawing apps, since Mantis has no coordinate click or draw tool. If you want a hosted browser that does not run on your machine, Kernel and Notte offer cloud browsers over MCP. If you want a finished agent that completes the task for you, look at Browser Use. Playwright MCP, the baseline Mantis measures itself against, is the other local option. Mantis is a v0.1 beta with 14 GitHub stars on 11 November 2026, and the maker states that page text reaches the agent unsanitised. More choices are on the browser skills list.
Version 0.1.13 (5 November 2026) added the attach-to-Chrome mode, and 0.1.14 (8 November 2026) made Mantis attach on its own when Chrome is listening on port 9222.
Protocol, authentication, permissions and the call contracts on this page are read from Perzian's own documentation. The verdict is Hokai's reading of them, not the maintainer's claim.