- Python 100%
| docs/superpowers | ||
| jev_gemlogin_bridge | ||
| tests | ||
| .env.example | ||
| .gitignore | ||
| pyproject.toml | ||
| README.md | ||
Jev + GemLogin Browser Bridge
Current version: 0.2.0
Bounded browser-control MCP server for using Jev to make typed decisions from the current GemLogin browser state. It starts a GemLogin profile, attaches Playwright over CDP, exposes the current page as bounded text and accessible elements, and sends that state to Jev for structured decisions.
Jev does not execute arbitrary code or click by itself. The available browser actions are intentionally limited to starting a profile, inspecting, navigating to HTTP(S), clicking by accessible role/name, and filling an accessible textbox or combobox.
Requirements
Install or prepare these before starting the MCP server:
- Python 3.11 or newer and
pip. - GemLogin Desktop / Local API, running on the same machine. The bridge
expects the default API at
http://localhost:1010. - At least one usable GemLogin profile ID, or an Android device managed by GemPhoneFarm.
- A valid Jev API key.
- An MCP-compatible host such as Codex, Claude Desktop, or another MCP client.
- Network access to the configured Jev endpoint.
For GemPhoneFarm phone control, GemPhoneFarm must be running at
http://localhost:1256, the device must be authorized in ADB, and the device
should be opened in the GemPhoneFarm scrcpy view once so the atx-agent port
forward is available for screenshots and UI hierarchy.
The optional GEMLOGIN_SCRIPT_LIBRARY environment variable points to a local
directory of .gemlogin/.ptcbrowser workflows. It defaults to
/Users/boombayah/Documents/Gemlogin/Script and is used only to inspect block
capabilities; workflow files are not uploaded or sent to Jev.
Playwright is installed as a Python dependency. No separate browser download is
required because the bridge connects to the browser launched by GemLogin over
CDP. If your environment uses Playwright independently, you may additionally
run python -m playwright install chromium.
Install
python3 -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
Dependencies used in the latest workflow
The latest GemLogin video-search workflow was verified on 2026-09-22 with the
following packages installed in the local .venv:
| Package | Version used | Role |
|---|---|---|
httpx |
0.28.1 | Call the GemLogin Local API and Jev API |
mcp |
1.30.0 | Expose the MCP server and tools |
playwright |
1.63.0 | Attach to the GemLogin browser over CDP |
pytest |
9.1.1 | Run the test suite |
pytest-asyncio |
1.4.0 | Test async browser/API code |
respx |
0.23.1 | Mock HTTP requests in tests |
The installation also brings transitive dependencies required by those
packages, including anyio, httpcore, pydantic, uvicorn, jsonschema,
and related support libraries. They are resolved automatically by pip from
pyproject.toml.
No new production dependency was added specifically for the video search: the
workflow used the existing MCP tools jev_gemlogin_start,
jev_gemlogin_navigate, and jev_gemlogin_inspect. No separate Playwright
browser binary was installed because Playwright attaches to the browser already
launched by GemLogin through CDP.
Configure the Jev API key
The key is read from the TYPESAFE_API_KEY environment variable. Put it in the MCP
client’s environment, not in the source code and not in Git.
This bridge now uses TypeSafe’s official Jev System One API and Python SDK. The
previous custom Jev endpoint is no longer used. Jev remains the model name, but
requests are sent through typesafe-sdk to https://api.typesafe.ai/v1/systemone.
Option A: temporary shell configuration
export TYPESAFE_API_KEY="your-real-typesafe-api-key"
export GEMLOGIN_BASE="http://localhost:1010"
export GEMPHONEFARM_BASE="http://localhost:1256"
export TYPESAFE_ENDPOINT="https://api.typesafe.ai/v1/systemone"
export TYPESAFE_MODEL="jev-latest"
Option B: local .env file
cp .env.example .env
Edit .env and replace replace-with-your-jev-api-key. The .gitignore
already excludes .env. This project does not load .env automatically; use
your MCP host's environment-file support or export the variables before
starting the server.
Never commit the real key. The jev_gemlogin_status tool only reports
whether a key is configured; it never returns the key.
Register with Codex
From the project directory:
codex mcp add jev-gemlogin-browser \
--env TYPESAFE_API_KEY="your-real-typesafe-api-key" \
--env GEMLOGIN_BASE="http://localhost:1010" \
--env TYPESAFE_ENDPOINT="https://api.typesafe.ai/v1/systemone" \
-- \
"$PWD/.venv/bin/python" -m jev_gemlogin_bridge.server
If the environment is already exported, the shorter form works:
codex mcp add jev-gemlogin-browser \
-- "$PWD/.venv/bin/python" -m jev_gemlogin_bridge.server
For other MCP clients, configure the equivalent stdio server command:
Command: /absolute/path/to/jev-gemlogin-bridge/.venv/bin/python
Arguments: -m jev_gemlogin_bridge.server
Environment: TYPESAFE_API_KEY=your-real-typesafe-api-key
Typical MCP flow
- Start GemLogin Desktop and confirm its Local API is available.
- Call
jev_gemlogin_statusto verify configuration. - Call
jev_gemlogin_start(profile_id)for one profile, orjev_gemlogin_start_many([profile_a, profile_b])for multiple profiles. - Call
jev_gemlogin_navigate(url, profile_id)or inspect the existing page. - Call
jev_gemlogin_inspect(profile_id)to read bounded page state and accessible elements. - Call
jev_decide(instruction, questions)with a bounded choice, score, or noul schema. - Inspect Jev's decision before invoking the selected safe action.
- Call
jev_gemlogin_clickorjev_gemlogin_typewith the targetprofile_idand inspect again after a page transition.
Before jev_gemlogin_task runs a profile, the bridge performs quarantine
preflight checks. A failed configured proxy or a detected Facebook checkpoint
blocks that profile and returns a proposed issue group. The profile is not
run again until jev_gemlogin_quarantine_release is called. Use
jev_gemlogin_quarantine_status to inspect blocked profiles, then explicitly
approve a move into an existing GemLogin group with
jev_gemlogin_quarantine_apply. The public GemLogin API does not expose group
creation, so the target group must already exist in GemLogin.
For independent work across profiles, call jev_gemlogin_task with one
objective and a profile-specific context map. Every task goes through the
supervisor: it creates one short-lived async worker for each profile, including
when only one profile is requested. Workers own their profile's queued browser
operations, run profiles concurrently, and return a result per profile. The
workers are stopped after the task; the underlying GemLogin profiles are not
deleted. It only supports HTTP(S) navigation and accessible click/fill actions;
posting, liking, commenting, sharing, messaging, and uploading are intentionally
unavailable.
The supervisor sends Jev a bounded DOM/accessibility snapshot kept in memory; it does not create screenshot files or browser-state files. The snapshot is trimmed to visible elements and bounded text before it is sent to TypeSafe. Common search actions use deterministic browser controls: the worker finds a visible search field, submits the query, and opens an external result link. Jev chooses the intent from state-aware options instead of choosing arbitrary Google settings or stale selectors.
Task results include bounded evidence collected locally by the bridge: visited page URLs, page titles, bounded page text, and deduplicated links with anchor text. Evidence is returned to the caller for verification and is kept out of the Jev decision payload. This applies to any browser task that produces links; the caller can use the evidence to produce a detailed summary with traceable sources.
To run a stored workflow directly, call jev_workflow_run with its workflow
ID, profile IDs, and parameter values. Use jev_workflow_status to poll the
run and jev_workflow_stop to stop it. The run tool passes through only the
workflow parameters plus explicit execution options; it does not read or
expose cookies, passwords, or API keys.
The server does not bypass login, extract cookies, expose passwords, or return the Jev API key.
GemPhoneFarm phone flow
The same MCP server exposes jev_phone_* tools for individual Android devices.
Use jev_phone_list_devices to get numeric device IDs, then use
jev_phone_dump_ui to read the foreground accessibility tree. Each node
includes text, content description, resource ID, bounds, a center coordinate,
and a selector. This structured data lets a non-vision Jev model locate and
tap controls. Custom Canvas, game, or remote-desktop surfaces may not expose
Android accessibility nodes.
jev_phone_list_devices()
jev_phone_dump_ui(device_id=7)
jev_phone_tap(device_id=7, x=540, y=700)
jev_phone_screenshot(device_id=7)
jev_phone_task performs a bounded inspect/decide/action loop for one device.
Direct actions use ADB; screenshots and UI hierarchy use GemPhoneFarm's
capture endpoint or atx-agent over the ADB forward. The standalone
gemphonefarm-mcp project remains available and is not modified by this bridge.
Google search procedure on a phone
When a phone task must search Google and open a result:
- Open a Google search URL with the encoded query, for example
https://www.google.com/search?q=gemlogin.io. - Dump the Android UI hierarchy after the results load.
- Do not click the editable search field just because its text matches the query.
- Select a visible clickable result card whose
content-desccontains both the target domain and title, such asgemlogin.io https://gemlogin.io GemLogin. - Tap the center of that node's
bounds. - Dump the hierarchy again and verify Chrome's URL bar contains the target
domain and no longer contains
google.com/search. - If the result moved, refresh the hierarchy and retry once. Do not submit forms, sign in, or click account/registration actions unless explicitly asked.
This distinction is important because Google exposes both the search input and the result card as clickable Android nodes. The query text alone is not a safe selector for the result.
Multi-profile example
jev_gemlogin_start_many(["facebook-profile", "news-profile"])
jev_gemlogin_task(
profile_ids=["facebook-profile", "news-profile"],
objective="ค้นหาข่าวน้ำท่วมล่าสุด",
profile_contexts={
"facebook-profile": "ใช้ Facebook",
"news-profile": "ใช้เว็บไซต์ข่าวที่เปิดอยู่"
}
)
The task returns one status and summary per profile. Profiles run concurrently, but operations within the same profile are serialized by that profile's worker. A disconnected profile is reported as failed or disconnected; the bridge does not reconnect it automatically. Publishing and communication actions remain unavailable.
Evidence and quarantine usage
When a task needs verifiable results, use jev_gemlogin_task. Each profile
result includes an evidence object containing visited pages and deduplicated
links. Review these URLs as the source trail for the final summary. The
evidence is collected by the bridge and is not passed to Jev for decision
making.
If preflight detects a proxy failure or a Facebook account issue, the result is
returned with status: "quarantined"; no browser actions are run for that
profile. Inspect the blocked profiles:
jev_gemlogin_quarantine_status()
After creating the matching group in GemLogin, explicitly approve the move:
jev_gemlogin_quarantine_apply(
profile_ids=["profile-id"],
group_name="Issue-Proxy"
)
The bridge does not create or delete GemLogin groups through the public API. It only moves quarantined profiles into an existing group. To allow a profile to run again, explicitly release it:
jev_gemlogin_quarantine_release(profile_id="profile-id")
Supported issue group names are Issue-Proxy,
Issue-Facebook-Checkpoint, Issue-Facebook-Login, and Issue-Unknown.
Tools exposed
| Tool | Purpose |
|---|---|
jev_gemlogin_status |
Show non-secret configuration and readiness |
jev_gemlogin_start |
Start a GemLogin profile and attach over CDP |
jev_gemlogin_start_many |
Start multiple profiles with per-profile results |
jev_gemlogin_sessions |
List non-secret metadata for started sessions |
jev_gemlogin_task |
Distribute a bounded non-publishing objective across profiles |
jev_gemlogin_quarantine_status |
List profiles blocked by proxy/account issues |
jev_gemlogin_quarantine_apply |
Explicitly move blocked profiles to an existing group |
jev_gemlogin_quarantine_release |
Explicitly unblock a profile |
jev_gemlogin_workflow_blocks |
Inspect observed GemLogin blocks, fields, and usage guidance |
jev_gemlogin_inspect |
Read URL, title, bounded visible text, and interactive elements |
jev_gemlogin_navigate |
Navigate to an HTTP(S) URL |
jev_gemlogin_click |
Click an accessible element by role and name |
jev_gemlogin_type |
Fill an accessible textbox or combobox |
jev_decide |
Ask Jev to choose among caller-defined typed options |
jev_workflow_run |
Start a stored workflow on selected profiles |
jev_workflow_status |
Check whether a workflow is running |
jev_workflow_stop |
Stop a workflow on selected profiles |
Test
Phone tools exposed by this bridge are jev_phone_status,
jev_phone_list_devices, jev_phone_device_info, jev_phone_screenshot,
jev_phone_dump_ui, jev_phone_tap, jev_phone_swipe,
jev_phone_press_key, jev_phone_type_text, jev_phone_start_app,
jev_phone_stop_app, and jev_phone_task.
python -m pytest tests -q
Security notes
- Keep
TYPESAFE_API_KEYin the MCP host environment or an untracked.envfile. - Do not commit cookies, browser profiles, passwords, or session exports.
- Use only profiles and websites you are authorized to access.
- Navigation is restricted to HTTP(S), and page text/elements are bounded before they are sent to Jev.