jev-mcp
Provides autonomous browser automation in Google Chrome, enabling agents to open URLs, click, scroll, type into fields, select options, and read visible text and page elements in a real logged-in browser profile.
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., "@jev-mcpgo to https://news.ycombinator.com and list the top 3 headlines"
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.
jev-mcp
jev-mcp is an MCP server that hands the browser agent jev-ultrafast to Claude Code and Claude Desktop. You give it a start URL and your goals in plain sentences, and it operates the page on its own in your real, already logged-in Chrome. A TypeSafe decision model (Jev) picks every step, and a separate text model writes the values that get typed into fields.
Requirements
Python 3.12 or newer
uvGoogle Chrome, driven through
browser-harnessA TypeSafe API key (
TYPESAFE_API_KEY). Without it no run starts at all, because nothing can choose an action, not even a click or a scroll.A text model key. Without it the agent can still click, scroll, navigate and pick from dropdowns, but it cannot type, so forms and search fields stay empty.
gitand access to GitHub during install. jev-ultrafast is not published on PyPI, so it is installed straight from its repository, pinned to the exact commit this server was tested against.
Related MCP server: Browser Tools for Claude Code
Setup
git clone https://github.com/SAITS-Swiss-AI-Tech-Services/jev-mcp.git
cd jev-mcp
uv syncPut the keys into ~/.config/jev-mcp/env. The format is NAME=VALUE, one entry per line. Empty
lines and lines starting with # are skipped, a leading export is stripped, quotes around a
value are removed, and a trailing comment is cut off. The file is read only when it is a regular
file of at most 256 KiB.
TYPESAFE_API_KEY=...
MOONSHOT_API_KEY=...Claude Desktop inherits no shell, which is exactly why this file exists. Claude Code does inherit the shell, so keys already exported there are found as well.
The server looks for the text model key by provider first and by source second. It tries
TEXT_MODEL_API_KEY, then Kimi (MOONSHOT_API_KEY, KIMI_API_KEY), then DEEPSEEK_API_KEY, then
OPENROUTER_API_KEY, and inside each of those steps the process environment before the file. The
first hit wins and decides the provider, including base URL and model name. For
TYPESAFE_API_KEY there is only one variable, and there the environment simply beats the file.
Register the server in Claude Code. Use the absolute path to uv:
claude mcp add jev-browser --scope user -- /absolute/path/to/uv run --directory /absolute/path/to/jev-mcp jev-mcpRegister it in Claude Desktop by adding the same command to
~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"jev-browser": {
"command": "/absolute/path/to/uv",
"args": ["run", "--directory", "/absolute/path/to/jev-mcp", "jev-mcp"]
}
}
}Both entries need absolute paths, because Claude Desktop does not inherit your shell environment
and therefore does not find uv on the PATH. command -v uv prints the path. Restart both
clients afterwards, they only load new servers on start.
Finally, allow Chrome to be driven. Once: open chrome://inspect/#remote-debugging and tick
"Allow remote debugging for this browser instance". After that, Chrome asks once per browser
session whether a connection may be made. See Troubleshooting for the reliable way to answer it.
The three tools
browser_task(url, goals, max_actions, time_budget_s, allow_domains, dry_run)
Operates a site autonomously: it opens url and pursues goals, deciding every click, selection
and keystroke without asking.
Parameter | Default | Limits |
| required | at most 2048 characters |
| required | a sentence or a list of them, at most 20 entries of 2000 characters each |
| 25 | 1 to 60, the library's own ceiling |
| 120 | 1 to 900, wall clock |
| none | at most 50 entries of 253 characters each |
| false | observes the page, fetches the first decision, executes nothing |
The result reports the status, the final URL, the page title, every executed step, the duration, the actions and model calls used, the reason it stopped and, for a dry run, the step it would have taken next.
browser_status()
Read-only diagnosis, and free: it opens no browser, loads no page and calls no model. It says which keys were found and where they came from, which text model would type, whether the browser-harness daemon is running and whether a Chrome is connected to it, and which operations are blocked as a result. Call it before the first run of a session and whenever one of the other two tools fails.
browser_read(url, time_budget_s, allow_domains, text_limit)
Opens one page and returns its visible text plus the table of operable elements, without clicking, typing or selecting anything.
Parameter | Default | Limits |
| required | at most 2048 characters |
| 30 | 1 to 900 |
| none | at most 50 entries |
| 4000 | 200 to 20000 characters |
The answer says how long the text was before truncation, and it lists at most 120 elements. A redirect onto another registrable domain is reported, and nothing from that page is returned.
Limits, stated up front
The agent underneath is an MVP, and these gaps are real:
It cannot see into iframes, and it cannot see into shadow DOM.
It cannot upload files.
It cannot follow pop-up tabs.
It only observes the visible viewport. It scrolls, but it never reads what is not rendered.
Only one run can be in flight at a time. A second call is refused immediately with
not_startedinstead of being queued, because a run writes process-wide values intoos.environand two runs would overwrite each other.A call blocks until the run ends.
The agent works inside your real, logged-in Chrome profile. Whatever it clicks happens in your sessions, for real. Each run opens its own background window and closes it afterwards. It does not use a background tab, because Chrome 153 answers no command sent to a background tab created over the DevTools protocol, while a background window works and leaves your own window alone.
Result messages are in German. The tool descriptions, which the model reads, are in English.
Do not put credentials into
goals. The goal text comes back verbatim in the result, and so does every value the agent typed into a field. Nothing of that is masked, on purpose: the typed text is the most important record of what the agent actually did. Password, file and hidden fields are excluded from observation, so nothing is typed there, but a one-time code or a customer number in an ordinary text field is not covered by that and will appear in the result.Logging in, paying, ordering and submitting forms on banking or payment sites are out of scope for this project.
The safeguard: domain fidelity instead of a blocklist
There is no blocklist. That was a deliberate decision: the agent only ever runs on request, so there are no forbidden domains. Instead a run remembers the registrable domain of its start URL. If a click or a redirect would take it to a different registrable domain, it stops and reports where it wanted to go, rather than acting there. Subdomains of the same registrable domain count as the same domain.
The check runs at two moments. Before a navigation it uses the address the next step would open, and
that is the check that actually protects you, because in a logged-in profile the loaded page is
already the damage: it has seen cookies, run scripts and sent requests. After every load it uses the
address the browser actually ended up on, which catches redirects, window.location from a script
and clicks the agent did not recognise as navigation. A block at that second moment means the damage
has already happened, and the run aborts instead of continuing.
When the module cannot read its own input reliably, it blocks. An unreadable start URL, a host with characters outside the permitted set, an invalid port, a numeric address that does not parse: all of those end in a block, never in a pass.
What it explicitly does not do:
It does not judge content. What an allowed page whispers to the agent is invisible to it, and page content can steer the decision model.
It knows no confirmation prompt and no step limit. Those live in the runner.
Before a click it can only read a target address from a real link (
a[href]). A<button>, a form submission or a click that only a script turns into navigation contributes nothing, and there the check first bites after the load.
Known weakness with hosting suffixes: the module does not use the Public Suffix List, which would be
a dependency. It applies the rule "the last two labels", extended by a built-in set of multi-part
suffixes that covers the common country suffixes and the common hosting suffixes such as github.io
and vercel.app. A multi-part suffix that is missing from that set, blogspot.de for example, is
read too loosely, and two unrelated sites under the same provider then count as the same domain.
The opposite error, a three-label host whose middle label happens to look like a suffix, makes the
check too strict, and the agent stops although it would have been allowed.
The check can be relaxed in four places: allow_domains per call, allow_domains globally in
~/.config/jev-mcp/policy.toml, the switch enforce_domain_lock = false in the same file, and
allow_unbound for runs that are meant to start without a domain binding.
Choosing the text model
Measured on 2026-09-20 through a real jev_ultrafast.model.field_text() call, filling the field
"Where from?" in a flight search. All four runs answered correctly ("Zurich"):
Model |
|
|
| 5855 ms | 5619 ms |
| 1156 ms | 1138 ms |
K3 costs five times as much latency, and it costs it per typed field. K3 is the default anyway,
by explicit decision. Switching is one line in ~/.config/jev-mcp/env, and the provider stays the
same:
TEXT_MODEL=kimi-k2.7-code-highspeedTroubleshooting
Call browser_status first. It names the missing key, the missing daemon or the missing Chrome
connection and says what is blocked because of it, which is faster than guessing.
If it reports that the browser-harness daemon is not running, or that it runs but no Chrome is
connected to it, browser-harness --doctor diagnoses install, daemon and browser state.
Chrome asks for approval once per browser session. The reliable way to answer it: bring a normal Chrome window to the front, not in full screen, then run the command below and click Allow in the dialog. It waits without a time limit.
cd /absolute/path/to/jev-mcp && echo 'print(page_info())' | uv run browser-harnessbrowser-harness mac-approve can click the dialog for you on macOS, but only when Chrome's
interface is in English: it matches the English dialog text. It also needs the Accessibility
permission for your terminal.
If a run is refused with not_started and a note about concurrency, another run is still in flight.
Wait for it and start again.
Development
uv run pytest
uv run ruff check .461 tests pass as of 2026-09-21. The suite runs without a browser and without network access, against test doubles and against contract tests that pin every assumption about jev-ultrafast to its installed source. An end-to-end run against real Chrome 153 through the server as a separate stdio process passed on 2026-09-21.
Built on
jev-mcp is a thin, guarded layer. The actual work is done by these projects:
Project | What it does here | License |
jev-ultrafast by Browser Use | The browser agent itself: the observe, decide, act loop | MIT |
browser-harness by Browser Use | Connects to Chrome over the DevTools protocol and keeps the session | MIT |
The decision model that picks every action and target | commercial API | |
The protocol Claude Code and Claude Desktop speak to this server | see repository | |
Server implementation and stdio transport | MIT | |
uv by Astral | Environment and dependency management | see repository |
The reference for how URLs are read, so the safeguard reads them the way Chrome does | CC BY 4.0 |
The text that gets typed into fields comes from an OpenAI-compatible model of your choice. The
server knows Moonshot Kimi, DeepSeek
and OpenRouter by name and works with any other endpoint through
TEXT_MODEL_API_KEY and TEXT_MODEL_BASE_URL.
Related: typesafe-mcp (MIT) exposes the same Jev model as a general judgment tool, typed answers with probabilities, for any MCP client.
Security
This server drives a real, logged-in browser, and its only safeguard is the domain check described above. If you find a way around it, please report it privately to hello@saits.ai instead of opening a public issue.
License
MIT, see LICENSE. Copyright (c) 2026 R. Schröder GmbH (SAITS - Swiss AI Tech Services).
Available Tools
3 toolsbrowser_readA
Open one page in the user's real, logged-in Chrome and return its visible text plus the table of elements that can be operated. This tool clicks nothing, types nothing and selects nothing. Opening the page is itself a request, though: it opens a background tab and navigates to url with the user's cookies, so a URL that acts on GET will act, for example a logout, an unsubscribe, a confirmation link or a one-click action. Right for reading or checking a page, for seeing what a page offers before starting browser_task, and for content behind a login the user already has. Wrong when anything has to be clicked or filled in (use browser_task), and wasteful for public pages that need no login (use a web fetch or search tool). Redirects are followed but verified: landing on another registrable domain is reported, and none of that page's text, elements, title or address is returned; widen that with allow_domains. The underlying library observes at most 6000 characters of visible text and cuts a longer page before text_limit (default 4000) applies, so the answer reports how long the observed text was and warns when it reached that ceiling. Labels and field values are capped at 200 characters and option lists at 50 entries per element, and the answer says when it capped something. Same limits as the agent: no iframes, no shadow DOM, no file uploads, no pop-up tabs, and only the visible viewport is observed, so content further down the page can be missing. Only one run can be in flight at a time.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | ||
| text_limit | No | ||
| allow_domains | No | ||
| time_budget_s | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden of behavioral disclosure. It does this exceptionally well: it explains GET side effects with user cookies, redirect verification and cross-domain blocking, text length capping, element/field caps, unsupported iframes/shadow DOM/popups, viewport-only observation, and the single-flight concurrency constraint.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but every sentence earns its place. It front-loads the core purpose and output, then organizes guidance into usage recommendations, redirect behavior, limits, and concurrency. No structured schema information is repeated, and there is no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity, an output schema already exists, and no annotations are present, the description is operationally complete. It covers side effects, explicit use cases, alternative tool routing, domain verification, truncation behavior, environment limitations, and concurrency. An agent has enough to know when and how to invoke it safely.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It adds real meaning to url by explaining cookie-aware navigation and GET side effects, to text_limit through the 4000-character default ceiling, and to allow_domains by showing it widens redirect acceptance. time_budget_s is not explained in detail, but its title and default are fairly self-explanatory, and the core parameters receive substantial semantic grounding.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'Open one page in the user's real, logged-in Chrome and return its visible text plus the table of elements that can be operated.' It clearly distinguishes itself from browser_task by stating it clicks, types, and selects nothing, and its role as a read-oriented tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use the tool: for reading or checking pages, for seeing what a page offers before starting browser_task, and for content behind the user's login. It also states when not to use it: when clicks or fills are required (use browser_task) and for public pages (use a web fetch or search tool). This gives the agent concrete routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
browser_statusA
Report whether a browser run can work at all, before spending time on one. Says which API keys are present and where they came from, which text model would type into fields, whether the browser-harness daemon is running and whether a Chrome is connected to it, and which operations are therefore blocked. Read-only and free: it opens no browser, loads no page, calls no model and costs nothing. Call it when browser_task or browser_read fails, before the first run of a session, or when the user asks whether the browser agent is ready. It says nothing about any particular website.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description fully carries behavioral disclosure. It clearly states the tool is read-only, free, opens no browser, loads no page, calls no model, and costs nothing. It also discloses the kind of state it inspects, which operations are blocked, and that it reports on readiness rather than site-specific behavior, giving the agent a complete operational picture.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The main point is front-loaded in the first sentence, and every subsequent clause adds distinct information about safety, prerequisites, or limitations. Though longer than typical descriptions, the density is high and there is no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers when to call, what it checks, what it avoids doing, and what it does not report. Given that an output schema exists and there are no parameters, nothing needed for correct invocation is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so the schema fully defines the call shape and the baseline is 4. The description reinforces that no arguments are needed by describing the tool as a global status check rather than a parameterized query.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: it reports whether a browser run can work at all. It enumerates concrete outputs (API key presence and provenance, active text model, daemon and Chrome state, blocked operations) and explicitly distinguishes itself from the siblings by noting it opens no browser, loads no page, and calls no model.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit trigger conditions: call it when browser_task or browser_read fails, before the first run of a session, or when the user asks whether the browser agent is ready. It also sets an exclusion boundary by stating it says nothing about any particular website, which prevents misuse.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
browser_taskA
Operate a website autonomously in the user's real, logged-in Chrome: open url, then pursue goals written as plain sentences, deciding every click, selection and keystroke without asking. Right for multi-step work on one site: filling a form, walking to a page that is only reachable by clicking, searching inside a site, reading something behind a session the user already has. Wrong for reading one page (use browser_read), for public pages that need no login (use a web fetch or search tool), and for logging in, paying, ordering or submitting anything on a banking or payment site. Never put credentials into goals: the goals and every typed value come back in the result. The run stays on the registrable domain of url and stops when a step would leave it; widen that with allow_domains. Bounded by max_actions (default 25, values above 60 are rejected) and time_budget_s (default 120 s, values above 900 s are rejected); the call blocks until the run ends, and only one run can be in flight at a time, including any run still finishing after its budget ran out. Set dry_run to get the first planned step without executing anything. The underlying agent cannot see into iframes or shadow DOM, cannot do file uploads, cannot follow pop-up tabs, and only observes the visible viewport, so it scrolls but never reads what is not rendered. The result reports the status, the final URL, every executed step and the reason it stopped.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | ||
| goals | Yes | ||
| dry_run | No | ||
| max_actions | No | ||
| allow_domains | No | ||
| time_budget_s | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
There are no annotations, so the description carries the full burden, and it does so thoroughly. It discloses that the call blocks until the run ends, that only one run may be in flight, that the tool runs in the user's real logged-in Chrome, that credentials surface in results, and that the agent cannot see iframes, shadow DOM, pop-ups, or off-screen content. It also states the stop behavior, domain confinement, and result contents.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long, but every sentence earns its place for a high-risk autonomous browser tool. It front-loads the core behavior and use cases, then sequences security warnings, constraints, limitations, and result reporting. There is no redundant or filler content.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with six parameters, no annotations, an output schema, and substantial side effects, the description is remarkably complete. It covers usage, constraints, concurrency, blocking, security risks, technical limitations, and return-value contents. An agent has everything needed to decide whether to invoke it and how to set its parameters correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must explain the parameters, and it explains all six. It defines `url`, `goals` as plain sentences with a security warning, `allow_domains` as domain widening, `max_actions` and `time_budget_s` with defaults and rejected bounds, and `dry_run` as planning without execution. No parameter is left to guesswork.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb-resource pairing: 'Operate a website autonomously in the user's real, logged-in Chrome,' then names the core inputs `url` and `goals`. It clearly differentiates from sibling tools by naming browser_read as the wrong choice for reading a single page. An agent can understand exactly what this tool does and what distinguishes it from nearby alternatives.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit positive use cases ('multi-step work on one site: filling a form, walking to a page that is only reachable by clicking') and explicit wrong-use cases with named alternatives ('use browser_read', 'use a web fetch or search tool'). It also adds hard exclusions for logging in, paying, or ordering on banking/payment sites and warns against putting credentials into `goals`. This is exemplary routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v0.1.0- First observed
browser_read - First observed
browser_status - First observed
browser_task
TDQS
Scored across 3 tools
The three tools are sharply delineated: browser_task performs autonomous multi-step interaction, browser_read is a read-only page fetch, and browser_status is a diagnostic readiness check. Both browser tools explicitly cross-reference when the other should be used, removing any ambiguity.
All tools share the browser_ prefix, making the family instantly recognizable. However, browser_read uses a verb while browser_task and browser_status use nouns, so the pattern is not perfectly uniform.
Three tools is a well-scoped set for the server's purpose: one for autonomous interaction, one for passive reading, and one for environment status. No tool feels redundant or extraneous.
The tool surface covers the full lifecycle of a browser automation session: verify readiness, read a single page, and execute multi-step tasks. Limitations are documented rather than missing functionality, and no obvious dead ends remain for the stated domain.
Maintenance
Related MCP Connectors
Real Chrome for agents: start a browser, read pages as numbered markdown, click, type, hand off.
Stealth web automation for AI agents. Login, signup, navigate, screenshot.
Stealth web automation for AI agents. Login, signup, navigate, screenshot.
Provides cloud browser automation capabilities using Stagehand and Browserbase, enabling LLMs to i…
Related MCP Servers
- AlicenseBqualityAmaintenanceEnables controlling a real Chrome browser from MCP hosts like Claude, with extension-based or CDP fallback, supporting tabs, navigation, interaction, and page reading tools.401,043 npm6MIT
- FlicenseNot gradedqualityDmaintenanceEnables browser automation (navigate, screenshot, click, type, etc.) for Claude Code via MCP protocol, with a Chrome extension for configuration.2-
- AlicenseNot gradedqualityBmaintenanceLets Claude Code drive your own Chrome browser with human-like clicks, drags, and typing. It uses your real sessions and logins, and pauses for your confirmation on anything irreversible.8MIT
- AlicenseAqualityCmaintenanceEnables AI agents to delegate complex web browsing goals to a real Chrome instance driven by Jev, completing tasks end-to-end in ~300ms per decision and returning only the final result.111MIT