clearcote-jet-mcp
OfficialThis MCP server lets an assistant run and continue browser-based web tasks in a Clearcote browser, then close the tabs it leaves open.
browse: carry out a natural-language goal on a website, starting from a URL or continuing an existing tab viatab_id, and return the page result as markdown.browsereports statuses such asdone,blocked,needs_input,budget, anderror.close_tab: close a tab thatbrowseleft open.
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., "@clearcote-jet-mcp find the next UK bank holiday on GOV.UK"
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.
Clearcote Jet
Tell it what you want done on a website. Jet reads the page, picks the next step in about a quarter of a second, does it with a real mouse and keyboard in Clearcote, and hands you the answer.

Three live runs, recorded in real time; the captions are added afterwards from each run's log. Every step shows what Jet did, how sure it was, and how long it took to decide. Watch the MP4.
Why use it
Fast. Each decision took 235 to 473 ms in our runs. Most of a run is the browser, not the thinking.
Cheap. A finished task averaged 17,973 input tokens: about $0.0008, or $0.76 per 1,000 tasks.
Learned once, then free. A task that worked is saved as a skill: the same goal from the same page then runs again with no model calls (24 of 24 replays in our runs), and where the page has changed, the agent takes over for that part and saves it again.
You can see why. Every step comes with the probability behind it and the options it passed over, so a shaky step stands out.
It uses the page like a person. Curved mouse paths, key-by-key typing, dropdowns picked from the open list, and a cookie banner is just another thing to click (it declines when it can).
One loop for every site. There is no per-site code: the same loop looks at the page again after every step.
Related MCP server: cloudflare-playwright-mcp
Try it in two minutes
git clone https://github.com/clearcotelabs/clearcote-jet && cd clearcote-jet
pip install -e .
cp .env.example .env # add TYPESAFE_API_KEY
python examples/run_task.py # watch it find the next UK bank holiday, cursor drawnJust the library: pip install git+https://github.com/clearcotelabs/clearcote-jet.
What it did in our runs
Live, in a visible Clearcote window, 29 September 2026:
Task | Result | Steps | Decisions | Time | Input tokens | Cost |
GOV.UK: find the next bank holiday in England and Wales | done: 25 December | 4 | 5 | 14.8 s | 13,654 | $0.0006 |
Python docs: search | done | 3 | 4 | 10.9 s | 29,048 | $0.0012 |
Python docs: switch the page to version 3.12 | done | 1 | 2 | 4.4 s | 19,349 | $0.0008 |
Hacker News: open the top story's comments | done | 1 | 2 | 2.7 s | 29,877 | $0.0013 |
RFC 9110: jump to the section on 404 Not Found | stopped at the 60-step cap | 60 | 60 | 92.3 s | 368,958 | $0.0155 |
The last row is a failure we kept on purpose: on a very long document it scrolled instead of using the table of contents. Jet now jumps to a section the goal names: on the same page it goes straight to "404 Not Found" in one step (13,793 input tokens, $0.0006, in 3 of 3 runs on 2 October 2026). For a goal that names no place it still scrolls, and stops after 12 scrolls in a row.
A GOV.UK run, step by step:
1. click "Reject additional cookies" p=0.85 decided in 361 ms
2. type "next bank holiday" into "Search" p=1.00
3. click "Search GOV.UK" p=0.93
4. click "UK bank holidays" p=0.99
done · 4 actions · 5 decisions · 14.8 s · 13,654 input tokens ≈ $0.0006What it costs
Decision model: $0.042 per million input tokens; output tokens are free. That is the published rate on 29 September 2026; set
CLEARCOTE_JET_USD_PER_MTOKif yours differs.Measured (seven everyday tasks, three runs each, 2 October 2026): 10,372 to 30,031 input tokens per finished task, so $0.0004 to $0.0013, about a quarter fewer than before that day's request trimming. A replayed skill: 0.
Not included: your Clearcote licence, and an optional text model, which bills with its own provider.
Your own runs: every result carries
usage(tokens, requests,estimated_usd), andpython examples/estimate_cost.pytotals everything inruns/and projects the cost per 1,000 tasks.
Learned once, replayed for free
The first time a goal finishes from a start page, Jet saves how it did it as a skill: each step with how to find its control again (role, label, and its place among controls labelled alike; numbers in a label may change, so "87 comments" is found again as "213 comments"), how the run ended, and how the page's list of results is read. The next time the same goal is run from the same page, Jet replays the skill with no model calls. A cookie banner that doesn't show again is skipped. If the page has changed (a button renamed, a step that is gone, an ending that doesn't match), the agent takes over from that point, decides only what changed, and saves the skill again.
If the results came from a JSON request, the skill keeps that request too, and a replay reads the rows with it from inside the page, without clicking at all.
2 October 2026, 8 tasks × 3 runs | Worked out step by step | Replayed |
Passed | 21 of 24 | 24 of 24 |
Model requests per task | 5.6 | 0 |
Input tokens per task | 17,450 | 0 |
Replays are not much faster: most of a run is the browser and its human-paced input, not the model.
Skills are JSON files in ~/.clearcote-jet/skills (CLEARCOTE_JET_SKILLS). On the command line, --no-skill works a
task out step by step and saves nothing; over MCP, browse(..., reuse=False) does the same, and list_skills /
forget_skill manage them. A skill is for one goal from one page: another search term is another skill, unless the
goal is a template (below).
A goal with {{slots}} in it, run with values for them, is learned as a template: "Log in as {{email}} with
password {{password}}" is one skill whatever the email and password, so it is learned once and replayed for every
account. The skill keeps a typed value as its slot ({{email}}), and a replay types its own values. A replay whose
skill has a slot you gave no value for stops at once with needs_input, naming the slot. A goal without {{...}} is
keyed exactly as before.
A slot can be secret: the model is shown the goal with that slot left as {{password}}, chooses to type the
token like any other word of the goal, and the keys pressed are the value's. The trace, the result, the saved skill
and every request to the model hold the token, never the value, even where the page shows it back.
Steps edited by hand
A skill can be edited, or written from scratch, and then replays exactly as written. Besides the recorded steps (a
control found by its role and label), an edited skill can go to an address, wait, wait until something is true, press a
key, run the page's own JavaScript, click or type into the first element matching a CSS selector, and wrap other steps
in if (with else) and while (at most max rounds) blocks:
{"steps": [
{"kind": "if", "condition": {"type": "text", "value": "We use cookies"},
"then": [{"kind": "click", "role": "button", "label": "Accept cookies"}], "else": []},
{"kind": "while", "condition": {"type": "control", "value": "Load more"},
"then": [{"kind": "click", "label": "Load more"}], "max": 10},
{"kind": "wait_for", "condition": {"type": "url", "value": "/results"}, "seconds": 15},
{"kind": "fill", "selector": "#q", "text": "{{query}}"},
{"kind": "key", "key": "Enter"},
{"kind": "js", "code": "document.querySelector('#more')?.click();"},
{"kind": "goto", "url": "https://example.com/next"},
{"kind": "wait", "seconds": 2}
]}A condition is {"type", "value", "not"?}: text (the page shows it), control (a control with that label is
there), url (the address contains it), selector (an element matches it) or js (the expression is true in the
page). An edited skill ends wherever its steps end, and a run that cannot get through them stops there with
diverged and the step number (skill.used is edited): it is never learned over.
clearcote-jet skill get --goal "Search for {{query}}" --url https://shop.example/ # {"ok", "skill"}
clearcote-jet skill put --goal "Search for {{query}}" --url https://shop.example/ < steps.json
clearcote-jet skill forget --goal "Search for {{query}}" --url https://shop.example/put checks every step first and says which one cannot run. The Clearcote Profile Manager's Tasks page edits a saved
task's steps this way.
Results without a model
When a run finishes on a page with a list of results (search hits, products, listings), items holds its rows, read
from the page's layout with no model: a title, a link, a price (the sale price, not a struck-out one) and an image,
each found with a selector that works for most rows. The navigation, header and footer are left out.
Use it
Python
import asyncio
from clearcote_jet import Session, run
async def main():
session = await Session.launch(headless=False)
page = await session.new_tab("https://www.gov.uk/")
result = await run(session, "Find the date of the next bank holiday in England and Wales.", page=page)
print(result["status"], result["url"], result["usage"]["estimated_usd"])
for step in result["trace"]:
print(step["step"], step["kind"], step["action"], step["probability"], step["alternatives"])
print(result["markdown"])
await session.close()
asyncio.run(main())Command line
clearcote-jet run --url https://docs.python.org/3/ --goal "Find the entry for asyncio.gather" --show-cursor
# keep one browser open and send it tasks
clearcote-jet browser --port 9222
clearcote-jet run --cdp http://127.0.0.1:9222 --url https://news.ycombinator.com/ --goal "Open the top story's comments"Flags: --show-cursor (draw the mouse), --keep-open, --headless, --no-humanize, --profile DIR,
--confirm (stop before a click that can't be taken back), --no-skill (don't learn or replay; see below),
--values JSON and --secret SLOT (a template goal and its values; see above):
clearcote-jet run --cdp http://127.0.0.1:9222 --url https://shop.example/login \
--goal "Log in as {{email}} with password {{password}}" \
--values '{"email": "anna@one.example", "password": "..."}' --secret passwordRunning a task across many profiles (worker)
clearcote-jet worker (or python -m clearcote_jet.worker) runs one task for another program, such as the Profile
Manager running the same task in many browser profiles: one worker process per profile and task. The program starts
it, writes one request line to its stdin, and reads events from its stdout, one JSON object per line. Stdout carries
nothing else (logs go to stderr), and it is plain ASCII.
{"cmd": "run", "cdp": "http://127.0.0.1:9222", "url": "https://shop.example/login",
"goal": "Log in as {{email}} with password {{password}}, then place the order",
"values": {"email": "anna@one.example", "password": "..."}, "secret": ["password"],
"confirm": true, "humanize": true, "reuse": true, "skills_dir": null, "max_steps": null}Field | Meaning |
| required: the running browser to attach to |
| required: the task, a template when |
| the start page, opened in a new tab of the worker's own (the tabs already open stay as they are); without it the first open tab is used, and nothing is learned |
| the slots' values, and which of them are secret |
| ask before a click that can't be taken back (default off) |
| human-paced mouse and keys (default on) |
| learn the task once and replay it with no model after (default on) |
| where skills are kept (default |
|
|
| at most this many actions (default 60) |
Events, by type:
| When |
| at once, with |
| after each action: the step as |
| with |
| the end: |
| the request was wrong, the browser could not be reached, or the task failed: |
status in the result is one of those under Reading a result, plus cancelled (a click was
not approved) and stopped (the program wrote {"cmd": "stop"}, which ends the task as soon as it can, at any time).
A secret value never appears in an event: it is written as its {{slot}}. The worker only disconnects at the end:
the browser and its other tabs keep running, and its own tab stays open for a look.
Reading values you can only describe
Say what you want in words and get back a selector for it, so everything after the first read needs no model at all:
clearcote-jet read --url https://example.com/product --want '{"fields":[{"name":"price","type":"money","ask":"the monthly price in euros"},
{"name":"available","type":"boolean","ask":"whether it can be ordered now"}]}'{
"selectors": {"price": "#buybox .a-price", "available": "#availability"},
"confidence": {"price": 0.96, "available": 0.91},
"values": {"price": "1.249,00 €", "available": "In stock"},
"missing": []
}The model never writes a selector. The page offers the values it can be read from, each with a
selector already proved to find it again; the field's own type throws out the ones it rules out — a
money field is not asking for "4 reviews" — and the model only chooses among what is left. So the
answer is always a selector that works on the page it was found on, and missing names anything it
would not guess at. A wrong selector is worse than none when it is going to be read from now on.
--cdp reads the page a browser already has open rather than fetching it again; --row SELECTOR
reads inside one repeating row and gives selectors relative to it, so they work on every row.
One read of two fields costs about 1,900 input tokens — $0.00008 at the published rate.
As a tool for your AI assistant (MCP)
{
"mcpServers": {
"clearcote-jet": {
"command": "clearcote-jet-mcp",
"env": { "TYPESAFE_API_KEY": "...", "CLEARCOTE_LICENSE_KEY": "..." }
}
}
}Your assistant gets these tools:
Tool | What it does |
| runs a whole task and returns every step, the rows of any list of results, and the page content. A task started from a |
| shows a tab exactly as Jet sees it: the numbered element table ( |
| one step by hand: |
| the tasks it has learned; forget one so it is worked out again |
| closes a tab |
When browse stops (blocked, needs_input), the assistant can look with snapshot, do a step with act, and hand
back to browse(goal, tab_id=...). act returns the new snapshot, so steps chain; if what a step depends on changed
since the snapshot, it does nothing and answers stale. Tabs stay open between calls, several tasks can run at once in
their own tabs (a second call on a busy tab answers busy), and the browser shuts itself down after a few idle minutes.
It also closes, with its temporary files, when the server stops (its input closed, Ctrl+C, Ctrl+Break, SIGTERM or
SIGHUP); not after a forced kill of the server, and on Windows a Ctrl+Break also ends Playwright's driver at once,
which leaves its playwright-artifacts-* folder in the temp directory. Screenshots are taken without touching the page's DOM; one over 200 KB is saved to a file and its path returned
instead of the image.
Guard rails: every tool carries MCP annotations (snapshot and list_skills only read; browse and act can change
things on websites), so a client can decide which calls need your approval. Only http/https urls are accepted,
read the way the browser reads them (file:, view-source:, chrome: and every other scheme are refused, always),
and this machine, the local network and cloud metadata addresses are refused: for the url a tool gets and for every
request the browser then makes (redirects, images, frames, script requests, popups; the browser's own chrome: and
chrome-extension: resources, such as its PDF viewer's, pass), unless CLEARCOTE_JET_ALLOW_PRIVATE_EGRESS=1 (needed
for local servers). Checking every request turns the browser's HTTP
cache off. Not covered: WebSocket connections a page script opens, a host name whose address changes between the
check and the browser's own lookup (DNS rebinding), a redirect in the very first load of a popup, and redirects of
requests made inside workers or cross-site frames. Every call has a time limit (typing gets extra time per character;
the tab stays open when it runs out), and page content comes back between <untrusted_page_content> tags that nothing
on the page can close or imitate: the text and title in one block, every element label (also where a status detail
or a progress message quotes one) in tags of its own, and an error the browser raised, which can quote the page, in a
block below the error's own lines.
Settings
Put them in .env (see .env.example) or the environment:
Variable | Default | What it does |
| required | key for the decision model (from console.typesafe.ai) |
|
| your Clearcote licence; without one the open build runs |
| unset | optional OpenAI-compatible model that writes typed values; without it Jet types words taken from your goal |
|
| browser profile; cookies and consent choices persist |
| unset | route through a proxy; timezone, language and location follow its exit IP |
| off |
|
| on |
|
| unset | attach to a browser that is already running |
|
| MCP server: close the browser after this long without calls |
|
| MCP server: seconds a |
|
| MCP server: the same for every other call ( |
| off | MCP server: |
|
| MCP server: the largest screenshot (bytes) sent as an image; a bigger one is saved to a file |
|
| MCP server: where those are saved; the newest 20 are kept |
|
| where learned tasks are kept, one JSON file each |
|
| rate used for the cost estimate |
Reading a result
Field | Meaning |
|
|
| every step: what was done, the text typed, its probability, the runner-up options and how long the decision took (replayed steps are marked |
| steps that were decided again because the page changed before Jet could act |
| decision-model tokens and requests, |
| the parts of the final page that answer the goal; for a run that did not finish, what was on screen |
| the rows of the final page's list of results (title, link, price, image), read without a model; empty if there is none |
| with skills: |
| with |
How it works
Look. Jet lists the controls a person could use right now: visible, enabled, not hidden behind a pop-up, including those inside embedded frames (iframes), same-site or cross-site. Password fields can be filled but are never read: Jet only sees whether one is empty, never what it holds. When it gets stuck on a page (the model finds no way forward there, or it has scrolled three times in a row), it also lists the links inside the page's closed menus: navigation dropdowns, menu panels and collapsed sections, each named by its menu (
Docs › Recommended settings). To click one, it opens the menu the way a person does, pointing at it (or clicking it, if pointing opens nothing), moves down inside the menu and clicks the link. It lists, too, what is scrolled out of view inside a box that scrolls on its own (a phone menu's panel, a dialog, a side list, a row of cards), which scrolling the page does not reach; it wheels over that box until the control is in view. While a field is being typed into, it lists the rows of the suggestion list open under it, each named by its field (From Station › Chongqing 重庆), even where those rows are plain elements with no role of their own — on a picker that keeps the real value in a hidden field (station and airport codes, address ids), clicking a row is the only thing that selects anything, and the field says so (suggestions=3) until one is picked.Decide. One request to the decision model picks the kind of step and the control together, with a probability for every option. The model only chooses from that list; it never writes code or coordinates.
Act. Jet checks that the page has not changed, then moves the mouse along a curved path, clicks, and types key by key. Page reads run in a separate, isolated context, so the page's own scripts don't see them. On a long page it can also jump to words from your goal, through the page's own anchor when there is one.
Answer. When the goal is met, the page is turned into markdown, and the sections around where the run ended are scored against the goal. A list of results on the page is read row by row, from its layout, with no model.
Remember. A finished task is saved as a skill (see below), so the next run of it needs no model.
Examples
File | What it shows |
the smallest program: one goal, the steps, the answer | |
any goal in a visible window with the cursor drawn; the run is saved to | |
four tasks on four kinds of site, with a summary | |
tokens and dollars for your saved runs, projected per 1,000 tasks | |
records live runs and turns them into a captioned MP4 and GIF (needs ffmpeg) |
Known limits
On a long page it jumps to a section the goal names; when the goal names none, it may scroll rather than use a table of contents, and stops after 12 scrolls in a row with
blocked.Links that open a new tab are not followed, and closed shadow roots, canvas apps, file uploads and CAPTCHAs are not handled. Controls inside iframes are; scrolling inside an iframe is not.
Links inside closed menus, and controls scrolled away inside a scroll box, are offered only once a run is stuck on a page, and one level deep: a submenu inside a menu is not opened, nor a box scrolled away inside another box.
doneis the model's judgement. Check what matters.Jet only types words that appear in your goal (unless you configure a text model), so write the values into it.
Built for privacy, testing, research and lawful automation.
Development
pip install -e . pytest ruff
python -m playwright install chromium # optional: the tests that need a browser use it, else they skip
ruff check . && pytest tests -q # offline: no network, no model calls
python tests/e2e/check_browser_layer.py # visible browser, scripted steps, a local test page
python tests/e2e/check_full_loop.py # the whole loop with a stand-in for the model
python tests/e2e/check_frames.py # controls inside same-site and cross-site iframes
python tests/e2e/check_mcp_tools.py # snapshot and act on a local form (one live step if a key is set)
python tests/e2e/check_mcp_stdio.py # the MCP server over stdio, as an assistant runs it
python tests/e2e/check_mcp_hardening.py # annotations, refused local urls, time limits, big screenshots to a file
python tests/e2e/check_skills.py # learn, replay, repair, lists, the request shortcut, confirm and find
python tests/e2e/check_worker.py # the worker as a process: a template learned, replayed with other values, approve and cancelLicense
MIT, see LICENSE.
Available Tools
2 toolsbrowseA
Carry out a task on a website in a Clearcote browser and return what the page says about it, as markdown.
Args: goal: What you want done, e.g. "Find the date of the next bank holiday in England and Wales" or "Search the Python documentation for asyncio.gather and open its entry". Write every value that has to be typed into the goal: typed text is taken from it. url: Start page. Required unless continuing an existing tab. tab_id: Continue in a tab returned earlier (a follow-up step, or after needs_input / blocked).
Status values: done, blocked, needs_input (the goal lacks a value a field requires: call again with the same tab_id and a goal that includes it), budget (step limit reached), error. The tab stays open so the user can see it; its tab_id is returned. The browser closes itself after a few idle minutes, which also closes its tabs.
Inputs: goal (required), url, tab_id. Returns: result
close_tabB
Close a tab that browse left open.
Inputs: tab_id (required). Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 2 tools
browse is a high-level agentic web task tool; close_tab is a cleanup utility for the tabs it creates. Their purposes do not overlap, so an agent can easily select the correct tool.
Both names are imperative snake_case verbs, but browse is a bare verb while close_tab follows a verb_noun pattern. This is a minor inconsistency, though overall naming is predictable.
Two tools is thin for a browser automation server, even one with a high-level browse design. The count sits at the borderline where the interface may feel under-specified.
The surface covers starting a task, handling follow-ups, and closing tabs. Minor gaps exist, such as no way to list open tabs or inspect browser state, but the agentic browse tool mitigates most needs.
Related MCP Connectors
Let your AI agent operate any website through a real, logged-in browser session.
Control a real browser from AI agents: navigate, click, type, screenshot, read the page.
Run multi-step tasks in a real Chrome browser: persistent environments, live view, human takeover.
A real browser for your agent: screenshots, page checks, Markdown and PDFs.
Related MCP Servers
- AlicenseNot gradedqualityNot gradedmaintenanceEnables AI assistants to control a browser through a set of tools, allowing them to perform web automation tasks like navigation, typing, clicking, and taking screenshots.Apache 2.0
- AlicenseNot gradedqualityNot gradedmaintenanceEnables AI assistants to control a browser through a set of tools, allowing them to perform web automation tasks like navigation, typing, clicking, and taking screenshots.Apache 2.0
- FlicenseNot gradedqualityBmaintenanceEnables AI assistants to browse websites, extract information, fill forms, interact with pages, and execute multi-step web tasks autonomously.-

Cloak Agentofficial
AlicenseAqualityBmaintenanceEnables an agent to run whole browser tasks on live web pages from a plain-language goal — opening tabs, clicking, typing, scrolling, and returning the relevant page sections as markdown — with every step reported alongside its confidence. Tabs can be reused and closed across calls, so multi-step flows continue on the page the previous task ended on.25MIT