Discover MCPs & agents
Loading MCPs and agents…
Loading MCPs and agents…
OpenAI-compatible API proxy for ChatGPT web — expose ChatGPT as an MCP server, REST API, and OpenAI SDK endpoint via CDP-driven browser automation
From the repo.

Turn ChatGPT into an API. No API key. No token extraction. No sentinel solving.
One command starts a Chrome browser, logs into ChatGPT, and exposes an OpenAI-compatible API + MCP server.
You have a ChatGPT Plus subscription but want to use it programmatically:
Other reverse proxies require token extraction, sentinel challenge solving, or cookie management. This one drives a real Chrome browser — anti-bot challenges (Turnstile, PoW) are handled automatically.
$ pip install chatgpt-web2api
$ chatgpt-web2api
✓ Chrome launched on port 9222
✓ Navigating to chatgpt.com...
✓ Ready on http://localhost:8080
$ curl -s http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"auto","messages":[{"role":"user","content":"What is 8+7?"}]}'
{"choices":[{"message":{"content":"8 + 7 = 15"},"finish_reason":"stop"}]}
# OpenAI Python SDK — drop-in replacement
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8080/v1", api_key="not-needed")
print(client.chat.completions.create(
model="auto",
messages=[{"role": "user", "content": "What is 8+7?"}]
).choices[0].message.content)
# → "8 + 7 = 15"
┌──────────────┐ OpenAI API ┌──────────────┐ CDP ┌──────────────┐
│ Your code │ ──────────────► │ API Server │ ────────► │ Chrome │
│ SDK / curl │ ◄────────────── │ or MCP │ ◄──────── │ chatgpt.com │
│ MCP client │ JSON / SSE │ Server │ events │ (logged in) │
└──────────────┘ └──────────────┘ └──────────────┘
The proxy types messages, clicks send, and reads responses — exactly like a human. The browser handles all anti-bot challenges transparently.
pip install chatgpt-web2api
# Start — opens Chrome, waits for login on first run
chatgpt-web2api
# Then use it:
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"auto","messages":[{"role":"user","content":"What is 2+2?"}]}'
# → {"choices":[{"message":{"content":"4"}}]}
That's it. One install, one command, one endpoint.
Drop-in replacement for api.openai.com:
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8080/v1", api_key="not-needed")
response = client.chat.completions.create(
model="auto",
messages=[{"role": "user", "content": "Hello!"}],
stream=True,
)
for chunk in response:
print(chunk.choices[0].delta.content or "", end="")
ChatGPT occasionally shows a "Too many requests" pop-up when conversations are accessed rapidly. ChatGPT-Web2API turns this from a silent failure into a practical, agent-friendly signal — so autonomous workflows back off and recover instead of breaking.
Transparent by default. When the pop-up appears, the server dismisses it (clicks "Got it") and retries your request with exponential backoff — up to 3 attempts. A transient limit is invisible: your agent gets a normal successful response and never knows it happened.
A clean pause signal when it persists. If the limit survives all retries,
the server surfaces a standard OpenAI 429 that every major agent framework
already knows how to handle — with zero client-side integration:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
{"error": {"type": "rate_limit_exceeded", "code": "rate_limit_exceeded", ...}}
The OpenAI SDK, LangChain, LlamaIndex, and similar frameworks auto-retry on
429 + Retry-After by default. The wait (Retry-After) is parsed from the
pop-up text when ChatGPT gives an exact number, otherwise a conservative 60s.
MCP clients (Claude Desktop, Cursor) get the same signal semantically: a
CallToolResult with isError=True and a machine-readable structuredContent
payload an agent can read to decide "pause, then retry this tool":
{"rate_limited": true, "retry_after": 60, "error": "rate_limit_exceeded"}
Streaming caveat: a 429 can be sent before streaming begins (a pre-flight check), but a limit that appears mid-stream (after the 200 is committed) is surfaced as an inline
[Error: rate_limit_exceeded — retry in Ns]chunk, since the HTTP status can no longer change. Non-streaming requests are unaffected.
ChatGPT changes its web API over time, which can silently break driver functions
(the unit-test mocks can't detect this). ChatGPT-Web2API ships a reactive
diagnostic that captures evidence at the moment a function breaks, plus a
doctor command that auto-discovers the breakage and prints the evidence for
fast repair.
Enable capture (off by default) so breakage in the wild is recorded:
W2A_DIAGNOSE=1 chatgpt-web2api start
When a function returns a broken shape, a redacted artifact is written to
~/.chatgpt-web2api/diagnostics/<function>-<timestamp>-<seq>.json with the
exact request, live response, expected-vs-actual mismatch (secrets stripped).
Auto-discover + diagnose:
chatgpt-web2api doctor # which functions have captured breakage?
chatgpt-web2api doctor create_project# print the captured evidence for one
chatgpt-web2api doctor --verify get_models # re-run a read function live to test a fix
doctor (no args) auto-discovers which functions are broken — no human
needs to name them. It then prints the evidence an AI coding agent (or you)
reads to propose the corrected payload/selector/parse. --verify confirms a
read-path fix against the live account before you commit it. No AI is bundled —
the project captures deterministic evidence; the fix is human-applied.
Expose ChatGPT to Claude Desktop, Cursor, Craft Agent, or any MCP client. Two transports are supported — SSE is recommended for persistent clients like ZCode; stdio is kept as a compatibility/dev mode.
SSE is useful for one long-lived MCP server, but current master does not yet
provide per-client tab/session isolation inside one SSE process. Multiple MCP
clients may attach to the same SSE endpoint, but they share one MCP server, one
CDPDriver, one Chrome tab, and one browser conversation surface. Running
parallel agent sessions through the same SSE server can cause cross-session
interference: one session may navigate, continue, or mutate the browser state
expected by another.
Until the B1 MCP session-affine tab pool is implemented and enabled, use SSE
for a single active agent session at a time. For parallel agent sessions
today, use separate MCP processes with parallel_tabs=true and
tab_mode=owned, and ensure the Chrome-owning chatgpt-web2api REST process
is started with the same parallel config or is not serving REST traffic on
that CDP port. Otherwise, wait for B1.
Start it (in a dedicated terminal — Chrome must already be running with an
authenticated session, i.e. chatgpt-web2api started first):
chatgpt-web2api-mcp --transport sse --port 8090
Then add the client config:
{
"mcpServers": {
"chatgpt-web2api-sse": {
"type": "sse",
"url": "http://localhost:8090/sse"
}
}
}
Every client session that attaches to that URL currently shares the single MCP server and Chrome connection. This avoids per-session process growth, but it is not safe for parallel independent agent sessions until B1's session-affine driver/tab pool is available and explicitly enabled.
Multi-session warning: A2 prevents stale-return by anchoring responses to the submitted turn, but it does not isolate multiple MCP SSE clients from each other. Shared SSE remains a single browser-control surface today.
Stdio spawns one MCP child process per client session, so it is not recommended for many concurrent ZCode sessions. It is useful for single-client setups, local development, and debugging (logs go straight to stderr):
{
"mcpServers": {
"chatgpt": {
"command": "chatgpt-web2api-mcp",
"args": ["--cdp-port", "9222"]
}
}
}
16 tools give the agent full control:
| Tool | What it does |
|---|---|
chat_completion | Send message, get response (multi-turn, streaming) |
list_models | 17 live model slugs (GPT-5.5, 5.4 Thinking, etc.) |
list_projects | ChatGPT projects with persistent memory |
create_project | Create isolated workspace with dedicated memory |
delete_project | Permanently remove a project (gated: W2A_ENABLE_DESTRUCTIVE) |
list_conversations | Recent chats with pagination |
get_conversation | Full message history |
archive_conversation | Archive/unarchive (reversible) |
delete_conversation | Delete permanently |
list_memories | Facts ChatGPT remembers (41 found in testing) |
create_memory | Tell ChatGPT to remember something |
delete_memory | Remove a memory |
list_gpts | Discover Custom GPTs |
chat_with_gpt | Chat with a specific Custom GPT |
update_project_instructions | Change project system prompt |
list_project_files | Files in a project's knowledge base |
Every tool includes rich descriptions with domain knowledge, Pydantic-validated input, structured output schemas, and proper ToolAnnotations — agents understand how and when to use each one without prompting.
The MCP server has no authentication of its own — any client that can reach it can call its tools. Since several tools mutate or delete data on your real ChatGPT account, tools are gated by risk tier so the dangerous ones are never exposed by accident:
| Tier | Tools | Exposed when |
|---|---|---|
| Safe (always visible) | chat_completion, chat_with_gpt, list_models, list_projects, list_conversations, get_conversation, list_memories, list_gpts, list_project_files | Out of the box |
| Write (account mutation) | create_project, update_project_instructions, create_memory, archive_conversation | W2A_ENABLE_WRITE=1 |
| Destructive (irreversible) | delete_conversation, delete_memory | W2A_ENABLE_DESTRUCTIVE=1 |
# Default: safe reads + core chat only — nothing can mutate your account
chatgpt-web2api-mcp
# Enable project/memory/archive creation
W2A_ENABLE_WRITE=1 chatgpt-web2api-mcp
# Full surface (also exposes deletes)
W2A_ENABLE_WRITE=1 W2A_ENABLE_DESTRUCTIVE=1 chatgpt-web2api-mcp
Gated tools are hidden from list_tools and refused at call time — a client that calls a gated tool by name gets an error result rather than silent execution. Binding the SSE transport to a non-loopback address with no api_keys configured logs a prominent warning.
ensureInstead of manually starting REST and SSE in separate terminals, run a single command that reconciles both and exits — ideal for a ZCode session-start hook:
chatgpt-web2api ensure
ensure is point-in-time reconciliation, not a supervisor: it checks REST
(/health) and SSE, starts whichever is missing, verifies SSE with a real MCP
handshake, and exits 0 when both are ready (nonzero with a diagnostic if not).
If a service dies later, re-run ensure from the next hook/session.
# Reconcile REST on 8080 + SSE on 8090 (defaults)
chatgpt-web2api ensure
# Custom ports / config (propagated to the child processes)
chatgpt-web2api ensure --rest-port 8081 --mcp-sse-port 8091 --config ~/.my-config.json
Degraded-REST policy: ensure does not restart REST immediately on
degraded (Chrome alive, driver disconnected) — it polls for up to 20s first,
since that may be a transient CDP reconnect. Only missing or broken trigger
an immediate (re)start. This avoids a destructive Chrome bounce on transients.
# Terminal 1: Start Chrome + API
chatgpt-web2api
# Terminal 2: MCP server (SSE recommended for ZCode; stdio also supported)
chatgpt-web2api-mcp --transport sse --port 8090
# or: chatgpt-web2api-mcp # stdio (one MCP child per client)
# Terminal 3: Any OpenAI SDK, curl, or HTTP client
curl http://localhost:8080/v1/chat/completions ...
| ChatGPT-Web2API | chat2api | ChatGPTReversed | Official API | |
|---|---|---|---|---|
| Anti-bot handling | ✅ Automatic (CDP) | ❌ Manual (PoW/Turnstile) | ❌ Manual | N/A |
| Token extraction | ❌ None needed | ✅ Required | ✅ Required | N/A |
| Project memory | ✅ Full CRUD | ❌ | ❌ | ❌ |
| MCP server | ✅ 16 tools | ❌ | ❌ | ❌ |
| ChatGPT memories | ✅ List/create/delete | ❌ | ❌ | ❌ |
| Custom GPTs | ✅ Chat with any GPT | ❌ | ❌ | ❌ |
| OpenAI SDK compat | ✅ Drop-in | ✅ | ❌ | ✅ (native) |
| Streaming | ✅ SSE | ✅ | ❌ | ✅ |
| Multi-turn | ✅ Auto-continue | ✅ | ❌ | ✅ |
| Installation | pip install | Docker | npm | pip install openai |
| Cost | Free (uses your subscription) | Free | Free | Pay-per-token |
Key insight: Other proxies fight the anti-bot system. This one uses the browser as the solution — Chrome handles Turnstile, PoW, and session management automatically.
An AI agent connected via MCP can:
# Create an isolated project with dedicated memory
create_project(name="Python Async Specialist", memory_scope="project_v2")
# Chat within the project (persistent memory across conversations)
chat_completion(message="What are Python coroutines?", project_id="g-p-abc123")
# Manage memories
list_memories() # → 41 facts ChatGPT remembers
create_memory(content="Always use type hints in Python code")
delete_memory(memory_id="abc-123")
# List and manage conversations
list_conversations(limit=10)
archive_conversation(conversation_id="xyz", archive=True)
# Chat with a Custom GPT
chat_with_gpt(gpt_id="g-hkJGhxxx", message="Analyze this data")
| Flag | Default | Description |
|---|---|---|
--port | 8080 | API server port |
--cdp-port | 9222 | Chrome debugging port |
--headless | false | Run Chrome headless (may trigger detection) |
--log-level | INFO | DEBUG, INFO, WARNING, ERROR |
config.json){
"port": 8080,
"host": "127.0.0.1",
"cdp_port": 9222,
"headless": false,
"default_model": "auto",
"default_project_id": null,
"tab_mode": "owned",
"parallel_tabs": false,
"api_keys": [],
"request_timeout": 120
}
See config.example.json for all available keys.
When mcp_session_pool_enabled=true, the MCP SSE server does not connect to
Chrome at startup. Instead, each MCP client session gets its own owned
CDPDriver/Chrome tab on first request (lazy materialization). Different
sessions get different tabs, capped by pool size. This enables parallel
multi-session use of one shared SSE endpoint.
Requires parallel_tabs=true + tab_mode=owned (validated at load).
{
"parallel_tabs": true,
"tab_mode": "owned",
"mcp_session_pool_enabled": true,
"mcp_session_pool_size": 2
}
Or via environment:
W2A_PARALLEL_TABS=1 \
W2A_MCP_SESSION_POOL_ENABLED=1 \
W2A_MCP_SESSION_POOL_SIZE=2 \
chatgpt-web2api-mcp --transport sse --port 8090
Pool defaults are conservative (size=2, TTL=30min). Raise the size only after the account-level burst canary shows no token-rotation errors or hard account challenges from genuinely simultaneous multi-tab use.
W2A_PORT=8080 W2A_CDP_PORT=9222 chatgpt-web2api
See .env.example for all available variables.
Live models from a ChatGPT Plus account (June 2025):
| Slug | Type | Best For |
|---|---|---|
auto | Reasoning | General use (default) |
gpt-5-5 | Reasoning, 34K context | Complex analysis |
gpt-5-4-thinking | Extended reasoning | Step-by-step logic |
gpt-5-3-mini | Fast, 34K context | Simple tasks, speed |
gpt-5-mini | Fast, 8K context | Quick answers |
Use list_models to get the current catalog.
| Operation | Time |
|---|---|
| Simple question ("2+2?") | 7s |
| Complex reasoning | 15–36s |
| Multi-turn follow-up | 3–6s faster (auto-continue) |
| Model listing | <1s |
| Memory listing | <1s |
src/chatgpt_web2api/
├── __main__.py CLI entrypoint (chatgpt-web2api)
├── config.py Configuration from file/env/CLI
├── chrome.py Chrome subprocess lifecycle
├── cdp_driver.py CDP primitives (24 methods)
├── api_server.py OpenAI-compatible HTTP server
├── mcp_server.py MCP server (16 tools, resources, prompts)
└── service.py Orchestrator: Chrome → CDP → API/MCP
The suite has two tiers:
Unit tests (default, CI) — fast, no browser, fully mocked. Run with pytest.
These cover structure, business logic, MCP protocol-level behavior, and access gating.
pytest -v # all unit tests (CI runs: pytest -m "not e2e")
End-to-end tests (opt-in) — drive a real ChatGPT account via Chrome CDP. These are the only tests that exercise the driver↔ChatGPT boundary, which is exactly where latent bugs hide (the E2E suite has already found four). They mutate the account, so they are deselected by default and never run in CI.
# 1. Launch Chrome with CDP (reuses your logged-in profile)
# "chrome.exe" --remote-debugging-port=9222 \
# --user-data-dir=~/.chatgpt-web2api/chrome-profile https://chatgpt.com/
# 2. Run the E2E suite
W2A_E2E_RUN=1 pytest -m e2e -v
E2E safety model:
W2A_E2E_PACE, default 12s) between tests to avoid ChatGPT's
"Too many requests" rate limit.create_project/update_project_instructions are xfail (broken
against the current live API — see CHANGELOG).If not logged in, the suite opens a headed window and waits up to 10 minutes for you to log in.
📋 Full documentation index — start here if you're unsure which doc to read.
Key docs:
parallel_tabs: true (requires tab_mode: "owned"); see docs/deployment.md → "Parallel mode (one Chrome, many tabs)"./backend-api/memories is read-only; creating memories works via chat interfaceparallel_tabs; the cross-instance pool/router is still future work)MIT © Elephant Rock Lab
Replace {MCP_ENDPOINT_URL} with this MCP’s endpoint URL (from its repo or docs above). No API key — you connect directly.
Tool
OS
Config file: ~/.cursor/mcp.json
{
"mcpServers": {
"mcp-server": {
"url": "{MCP_ENDPOINT_URL}"
}
}
}Paste into mcpServers in the config file. Restart Cursor after saving.
If this MCP is also published on mcpchannel.ai, you can subscribe from Browse and use the gateway config there instead.