MCP-LinkGPT
Allows Codex to consult ChatGPT Web through a dedicated browser session, providing tools to check login/status, start a new chat, send a prompt and wait for the response, retrieve the latest assistant response, and close the browser session.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
MCP-ChatBridge
MCP-ChatBridge is a small local MCP server that lets Codex explicitly consult
three browser-based AI advisors through deterministic browser-use/CDP control:
ChatGPT Web
Gemini Web
Claude Web
Codex remains responsible for gathering evidence, deciding what to change, implementing changes, and verifying them. The browser bridge is intentionally not an autonomous browser agent.
┌─ ask_chatgpt ─> providers/chatgpt.py ─┐
Codex -> MCP-ChatBridge ----├─ ask_gemini ─> providers/gemini.py ├─> common bridge/session lifecycle -> browser-use/CDP
└─ ask_claude ─> providers/claude.py ─┘The shared lifecycle owns profile locking, browser startup/shutdown, cancellation behavior, CDP target binding, page identity, response ownership, correlation, timeout handling, and fail-closed cleanup. Provider adapters own only the parts that differ by site: approved domains, home/login routes, DOM selectors, composer/send controls, streaming signals, and conversation routes.
MCP tool surface
The MCP server exposes exactly three top-level tools:
ask_chatgpt(...)ask_gemini(...)ask_claude(...)
Each tool uses the same interface and supports these action values:
ask— send one prompt and wait for a confirmed final response.status— open/check the dedicated provider profile without sending a prompt.last_response— bounded recovery after an explicit timeout; never resends.new_chat— open a fresh conversation.close— close only that provider's browser session.
This keeps the MCP surface at three tools instead of multiplying provider lifecycle operations into 15 top-level tools.
Image generation (ask_gemini and ask_chatgpt)
ask_gemini and ask_chatgpt additionally accept action="generate_image". It is
not a new tool; Claude Web rejects the action. The examples below use Gemini;
ChatGPT specifics are in "ChatGPT image generation and attachments".
ask_gemini(
action="generate_image",
prompt="Generate an image: <explicit image request>",
save_dir="", # default ~/Pictures/MCP-ChatBridge/gemini ; absolute or ~/..., outside this repo
max_images=4, # 1-8
timeout_seconds=600
)The prompt goes through the same fail-closed ask lifecycle (serialized
operation, pinned page identity, observable submit acceptance, no automatic
resend). After the owned response is stable, the bridge runs a collection step
inside the same locked operation:
Enumerate every
<img>in the owned assistant turn (no Gemini-specific image selector), then filter inimage_store.py: visible, at least 128 px on the short side, allowed host (*.googleusercontent.com,*.gstatic.com,*.google.com, or page-localblob:/data:image), de-duplicated. When the UI changes, adjust that one policy instead of a selector chain.Download, in order of preference:
Full size: click the page's own download control (found by its
downloadicon name, scoped to the one image it belongs to) while Chrome downloads are redirected by CDPBrowser.setDownloadBehaviorinto a private temp dir, then read the file and delete the dir (source="full_download",method="download_button").Fallback: in-page
fetch(original-size=s0URL, then the displayed URL), then a canvas re-encode of the displayed image (displayed size only).
Cookies are never read or exported.
Validate by magic bytes (PNG/JPEG/WEBP only, 16 MB cap) and save as
gemini-<UTC time>-<sha256 prefix>.<ext>with file mode0600.
Results (image bytes are never returned, only metadata):
ok=true,status="completed",images=[{path, mime, bytes, sha256, source, method, ...}],image_count,save_dir, plus any text inresponse(may be empty for an image-only turn).image_errorslists images that failed while others saved.ok=false,status="no_image",error_type="NoImageGeneratedError"— Gemini answered without a usable image (refusal, quota, wording). The text is inresponse; nothing is resent.ok=false,status="completed_collection_failed"— the response completed but no image could be saved. Uselast_response; do not resend the prompt.
Phrase the prompt as an explicit image request; the bridge does not rewrite it.
Always use a detailed prompt (format, scene, camera, materials/lighting, hex palette per
part, strict negatives). Short prompts gave small centered compositions with empty side panels;
the detailed prompt gave a full-bleed render. See
GEMINI_IMAGE_PROMPT_GUIDE_TH.md for the template, a worked
example and fixes for common defects.
Attaching images (attach_images)
ask_gemini(action="ask" | "generate_image", attach_images=["/abs/path.png", ...])
attaches up to 4 local PNG/JPEG/WEBP files (absolute or ~/ paths, 16 MB each)
so Gemini can describe or edit them (image-to-image).
Paths are validated before any browser work: regular file, image magic bytes, size cap, de-duplicated. A bad path stops the call with nothing sent.
Inside the locked operation, before the prompt is typed: open the attach menu (control found by its
plusicon name, nearest to the composer), set the files on the page'sinput[type=file](prefers theimage/*one) with CDPDOM.setFileInputFiles, then wait until the previews appear and the upload spinner is gone. If that fails the call stops before submitting.The files are uploaded to Google. Attach only images the user asked to send.
Language independence
Send and stop buttons, the attach trigger, and the download control are matched by
icon name (arrow_upward, stop, plus, download), not by localized
aria-label, so a non-English Gemini UI keeps working. The old English labels
stay as extra selectors.
Live verification (2026-10-31)
generate_imagesaved a full-size JPEG (2752x1536) through the download control, versus the 1024x572 displayed size; the temp download dir was removed.askwith one attached PNG: Gemini described the attached image correctly.generate_imagewith one attached PNG: produced a 2750x1536 redraw of it.Icon-based selectors matched the live send button (typed text) and stop button (while responding).
Gemini's fresh-chat route appears ~9-11s after Enter, past the old fixed 8s wait.
submit_timeout_seconds(how long_submitwaits for an observable acceptance signal) is now 120 for every provider, a user decision because the services are sometimes slow. It only delays a failing submit; the lifecycle still never resends.
Transcript mode (ChatGPT, Gemini, Claude)
Page DOMs drift constantly. Live on 2026-10-31 ChatGPT dropped <article> and
data-message-author-role, so the per-turn counters were always 0 and every ask
failed with "no observable lifecycle acceptance signal" although the message had
been sent and answered. All three providers therefore run in transcript mode
(ProviderConfig.transcript_mode): the bridge reads the whole conversation
text and watches only generic signals, and the caller reads the text (noise
included, such as sidebar and footer text).
_page_stateaddspage_text_len,page_text_tailandpage_imagesfor themainelement (falling back tobody), with the composer form excluded so typing and submitting do not move the numbers.Submit acceptance also accepts page-text growth (besides the old counters and the stop control).
_submitpolls the page every 0.15 s and returns the moment a signal appears;submit_timeout_seconds(120) is only a ceiling. It presses or clicks send once and never resends.The response is finished when the text and images stop changing: 3 s after a stop control disappears when one was ever seen, otherwise
transcript_stable_seconds(8 s). An image request also needs a new image beyond the ones the user attached; with no image it reportsno_image3 s after the stop control (45 s when none was ever seen).responseis the whole conversation text andtranscriptistrue. A fresh chat contains only our exchange;new_chat=falseincludes earlier turns.last_responsereturns the current page's conversation text.Response ownership is unchanged: serialized operation, pinned page identity (the provisional route may move to the final route once), observable submit acceptance, no automatic resend.
A page whose path starts with
/login,/signin,/sign-inor/auth/loginis reported as a login gate whatever its DOM looks like.Images: every
<img>on the page outside the composer form that was not there before submit is a candidate; the first N (the images you attached, which render before any generated image) are skipped and the lastmax_imagesare kept.
ChatGPT image generation and attachments
ask_chatgpt(action="generate_image", ...) and attach_images work like the Gemini
versions, with these ChatGPT specifics (verified live on 2026-10-31 with one
attach + generate run that saved a 1678x937 PNG):
Free accounts can create only a few images (and uploads) per day. Do not use this for casual tests; when the limit is reached the reply is text and the result is
status="no_image", never a resend. Preferask_geminifor images.Default folder
~/Pictures/MCP-ChatBridge/chatgpt(Gemini keeps.../gemini).ChatGPT has no inline download button (only "Generated image N", "Edit ...", "Share ..."), so the image bytes are fetched from the page; they are the full-size file, not a thumbnail.
Attaching: the page keeps four file inputs from load and only the one without an
acceptattribute attaches. A file set immediately after the page loads was silently ignored twice, while one set after a 3 s settle attached in about 2 s, soattach_settle_secondsis 3 and the files are re-issued (up to 3 tries, 6 s apart) if nothing changes. Nothing is sent if the upload never shows.A fresh chat moves
/to/c/local-chatgpt%3A<uuid>to/c/<uuid>; the provisional prefix/c/local-chatgptis configured, otherwise the submit check saw an unknown route.Claude Web: needs a manual sign-in in its profile window. Claude's transcript-mode
askwas verified live after that sign-in.
Sign-in hosts (all providers)
A future sign-in must be able to finish inside the bridge-controlled window. Before
this was added, Claude's window blocked a Google sign-in and kept reopening (seen
live on 2026-10-31). Each provider now lists its sign-in hosts as authentication
hosts only: the page may open there and the bridge reports a login gate, but they
are never interactive_roots, so the bridge never types, clicks or mutates them.
Provider | Extra sign-in hosts |
ChatGPT |
|
Gemini | Google ( |
Claude |
|
accounts.youtube.com is part of Google's sign-in redirect chain; that is general
knowledge and has not been exercised live here. Complete CAPTCHA or verification
prompts manually; the bridge still stops on them.
Provider selection
No provider is the default for advisor, review, or second-opinion requests, and a
plain advisor request is not a ChatBridge request (see the repository CLAUDE.md
routing rule). Use these tools only when the user says to use them via ChatBridge
or names a provider in that session: plain "via ChatBridge" means ask_gemini;
ask_chatgpt and ask_claude only when the user names them. Claude's image
generation always uses ask_gemini with action="generate_image". If the
requested provider is unavailable, report the reason and obtain explicit
direction before switching providers; never switch silently.
Example:
ask_gemini(action="status")
ask_gemini(
action="ask",
prompt="<complete focused request>",
new_chat=true,
timeout_seconds=600
)
ask_gemini(action="last_response")
ask_gemini(action="close")A successful advisory answer must have:
ok = true
status = "completed"Progress notifications and visible response tails are context only. They are not final advice.
Related MCP server: MCP-CodexGPT
Migration from the previous ChatGPT-only surface
The Agentic version now uses the three-tool surface above. Previous top-level names such as:
chatgpt_status
chatgpt_ask
chatgpt_last_response
chatgpt_new_chat
chatgpt_closeare no longer MCP tools. Their behavior moved behind ask_chatgpt(action=...).
For example:
chatgpt_status() -> ask_chatgpt(action="status")
chatgpt_ask(prompt="...") -> ask_chatgpt(action="ask", prompt="...")
chatgpt_last_response() -> ask_chatgpt(action="last_response")
chatgpt_new_chat() -> ask_chatgpt(action="new_chat")
chatgpt_close() -> ask_chatgpt(action="close")Provider profiles
Each provider owns a separate persistent Chrome profile:
ChatGPT ~/.codex-chatgpt/browser-profile
Gemini ~/.codex-gemini/browser-profile
Claude ~/.codex-claude/browser-profileOptional overrides:
export CODEX_CHATGPT_PROFILE_DIR="$HOME/.codex-chatgpt/browser-profile"
export CODEX_GEMINI_PROFILE_DIR="$HOME/.codex-gemini/browser-profile"
export CODEX_CLAUDE_PROFILE_DIR="$HOME/.codex-claude/browser-profile"Profiles must remain outside the repository. They contain authenticated browser state and must never be committed or shared.
Only one MCP process can own a given provider profile at a time. The providers do not share locks or authenticated state with one another.
Deterministic security model
MCP-ChatBridge intentionally keeps browser control narrow.
For every provider:
Automatic navigation is restricted to the provider adapter's approved interactive/login domains.
Every DOM mutation rechecks HTTPS, port, credentials-in-URL, hostname, and the bound page identity before changing the composer or submitting a prompt.
CDP work is pinned to one browser target for the duration of an operation.
CAPTCHA, login, and human-verification gates stop the bridge for manual user action; they are never bypassed.
Browser cookies, passwords, prompts, and responses are not written to application logs or exported through storage APIs.
If startup/shutdown/cancellation leaves browser ownership uncertain, the profile stays blocked rather than being silently reused. A persisted marker older than the recovery grace may self-clear only while the cross-process profile lock is held, process inspection succeeds, and no process names that exact
--user-data-dir; any live owner or uncertain scan remains blocked.A cancellation after prompt submission is not treated as a rollback.
The shared lifecycle continues to disable browser-use's storage-state cookie watchdog for persistent profiles. Chrome itself owns the profile state.
Gemini's Quill editor and Claude's ProseMirror editor are driven through native
CDP input events (Select All/Backspace, Input.insertText, then Enter) rather
than DOM execCommand mutation. This keeps the visible composer and the site's
internal rich-editor state synchronized for long and multiline prompts. Enter is
the single native submit action; if lifecycle acceptance is delayed or ambiguous,
the bridge waits through the submit deadline and fails safely instead of issuing a
second DOM-button click that could duplicate the turn.
Response ownership and correlation
The bridge does not accept "the latest text on the page" as sufficient proof that a response belongs to the prompt it just sent.
Before submission it verifies only structural readiness: the guarded composer is still present, visible, and non-empty. It deliberately does not compare the rendered DOM text with the source prompt because Quill, ProseMirror, and other rich editors may rewrite whitespace, Markdown blocks, or accessibility text. After submission ownership is established from lifecycle signals:
the bound browser target and serialized operation;
the pinned page/conversation identity;
an observable submit-acceptance signal (new user turn, new assistant turn, or streaming state); for a fresh-chat route, the bridge first baselines the new conversation and requires a later lifecycle mutation on that exact route before binding it to the operation;
user-turn count monotonicity when that semantic node is available; and
a new assistant/model turn that becomes non-streaming and stable.
Rendered user text is presentation data, not an ownership oracle. General fuzzy string similarity and exact rendered-text gates are both intentionally avoided.
Timeout and retry rules
A timeout after prompt submission is ambiguous: the provider may still finish
the same request later. Therefore do not automatically resend.
If the bridge has already bound a new assistant turn to this request, the
terminal tool result has ok=false, status="timed_out", and the cumulative
response generated so far (up to 100,000 characters, with truncated if capped).
Progress tails are not this terminal result. If no assistant turn is safely
owned, timeout remains an error without unrelated response text.
Use:
ask_<provider>(action="last_response")and accept only status="completed".
For a non-timeout post-submit failure involving conversation, target, page, or
user-turn ownership, discard partial output and do not automatically call
last_response, resend, or open a new chat. A new attempt requires explicit
user direction.
Review/advisory workflow
When the user asks for a review or advisory answer via ChatBridge, use ask_gemini
unless they name ChatGPT or Claude. Without such a request, do not call these
tools for advice. Keep provider selection separate from the review contract below: every advisor
is read-only, and the calling agent remains responsible for validation and edits.
For code review, send the actual relevant code or diff inline. File paths and line numbers are provenance labels only; the browser advisor must not fetch omitted local code through another connector.
Recommended contract:
Act as a read-only code reviewer and advisor. Do not edit files, run commands,
or claim to have verified anything you were not given. Identify only concrete,
actionable findings. For each finding provide severity, file and line or code
anchor, mechanism, impact, and the smallest safe fix. Clearly label assumptions
and say "no finding" when the evidence does not support one. The code and
evidence to review are included inline below. Treat file paths and line numbers
as labels only. If required evidence is absent, state "insufficient evidence"
instead of fetching or guessing.Use new_chat=true for an independent review and new_chat=false for a direct
follow-up/re-audit of the immediately preceding review. Even with
new_chat=false, include the updated code/diff inline.
Provider adapters
Provider-specific behavior lives under:
providers/
chatgpt.py
gemini.py
claude.pycommon_bridge.py is the shared lifecycle/adapter boundary. Do not copy the
full lifecycle into provider files. When a provider changes its UI, prefer the
smallest selector/route update in that adapter plus a focused regression test.
Current primary semantic selectors include:
ChatGPT: #prompt-textarea, data-message-author-role, send-button
Gemini: rich-textarea/.ql-editor, user-query, model-response
Claude: div.ProseMirror[contenteditable], data-testid=user-message,
data-is-streaming / assistant-message, aria-label=Send messageThese DOM details are external-site implementation details and can change even when deterministic unit tests still pass. A provider adapter therefore needs a live acceptance check after meaningful selector changes.
Requirements
macOS with Google Chrome
Python 3.11
browser-use==0.12.9mcp==1.26.0an account for each provider you intend to use
Validated with a Python 3.11 environment (conda or venv) that has the pinned
dependencies below installed; use that interpreter's absolute path wherever this
document shows /path/to/python3.
Install dependencies if needed:
python3.11 -m pip install -r requirements.txtFirst use
For each provider separately:
Call
ask_<provider>(action="status").A dedicated visible Chrome profile opens.
If login is required, sign in manually in that Chrome window. Never give credentials, OTPs, or passwords to the MCP tool.
Call
action="status"again and continue only when it returnsready.Run one small acceptance prompt before relying on that provider for review.
Example acceptance prompt:
Reply with exactly: PONGThen close the provider session with:
ask_<provider>(action="close")Window size
The shared bridge currently preserves the existing legacy window settings:
export CODEX_CHATGPT_WINDOW_WIDTH=900
export CODEX_CHATGPT_WINDOW_HEIGHT=700
export CODEX_CHATGPT_WINDOW_X=100
export CODEX_CHATGPT_WINDOW_Y=120They apply to all three provider windows. The default remains 760x560 at
position 24,60.
Test
Run the complete deterministic suite with the interpreter that has the dependencies installed:
cd /path/to/MCP-ChatBridge
BROWSER_USE_CONFIG_DIR=/tmp/mcp-chatbridge-test-browseruse-config \
PYTHONDONTWRITEBYTECODE=1 \
/path/to/python3 -m unittest discover -s tests -vThe suite covers the original hardened ChatGPT lifecycle, provider isolation, origin guards, provider routing, the three-tool MCP schema, timeout-recovery actions, and an actual stdio MCP initialization/list-tools handshake.
Unit tests cannot prove that a third-party web UI has not changed. Live tests should therefore be deliberately initiated after manual login for ChatGPT, Gemini, and Claude before treating a newly added/changed adapter as production ready.
Codex registration
Register the stdio server with absolute paths:
codex mcp add mcp-chatbridge -- \
/path/to/python3 \
/path/to/MCP-ChatBridge/server.pyCodex applies its own per-tool timeout outside MCP-ChatBridge. The bridge permits
600-900 second browser reviews, so configure a host timeout at least as long as
the largest timeout_seconds you intend to request:
[mcp_servers.mcp-chatbridge]
tool_timeout_sec = 900.0Restart Codex after changing registration or timeout settings so the MCP tool schema refreshes to:
ask_chatgpt
ask_gemini
ask_claudeThis server cannot be deployed
Maintenance
Related MCP Connectors
Connect AI agents to your own Chrome with scoped access, approvals, and verified task results.
Control your Chrome from chat: open websites, read what's on the page, click buttons, and fill in f…
241Persistent cross-session memory shared by Codex, Claude Code, ChatGPT, and other AI agents.
Shared workspace where Claude, ChatGPT, Codex and Grok share files, tasks, decisions and handoffs.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables ChatGPT Pro web to securely read workspace files, git diffs, and test records through OAuth-protected read-only MCP tools, so it can plan and review while Codex handles execution without uploading the repository.MIT
- AlicenseNot gradedqualityCmaintenanceEnables Codex to consult ChatGPT Web through a dedicated Chrome session, acting as a read-only reviewer and senior advisor for code, tests, and designs.MIT
- AlicenseNot gradedqualityBmaintenanceEnables Codex to delegate tasks to ChatGPT through a browser-backed Oracle connection, with project memory and a verified-result cache. It supports preview, delegation, result polling, and explicit acceptance without an API fallback.1MIT
- AlicenseAqualityBmaintenanceEnables Codex to use an authenticated ChatGPT Web session as a reasoning and coding backend through isolated browser sessions, without granting ChatGPT direct workspace access.3111 npmMIT