Skip to main content
Glama

TophatMCP

TophatMCP is an authenticated OpenAI-compatible inference gateway backed by an async job broker that bridges a coding agent to a ChatGPT MCP worker.

A coding agent talks to TophatMCP exactly as if it were any OpenAI-compatible provider (POST /v1/chat/completions). TophatMCP enqueues the request as an InferenceJob and blocks the HTTP request until a ChatGPT worker — connected over a tunnel-presented /mcp surface — claims the job and submits a result.

flowchart TD
    A[Coding Agent e.g. Qwen Code] -- "POST /v1/chat/completions" --> B(TophatMCP Gateway)
    B -- "enqueue InferenceJob (blocks HTTP)" --> J[(Job Broker / SQLite)]
    J -- "pending claim resolves atomically" --> C{ChatGPT MCP Worker}
    C -- "submit_inference_result" --> J
    J -- "resolve waiter / HTTP 200" --> A

TophatMCP never executes the coding agent's tools. It merely preserves the protocol that lets the coding agent execute them locally and feed results back in.

Why a job broker (not a synchronous proxy)

The OpenAI Secure MCP tunnel presents TophatMCP's own /mcp surface to ChatGPT — ChatGPT dials in. A synchronous /v1 call therefore cannot traverse the tunnel by calling an outbound function. The rendezvous is inverted into an async job queue:

  1. The coding agent hits POST /v1/chat/completions. TophatMCP enqueues an InferenceJob and blocks the HTTP request.

  2. The ChatGPT worker (via tunnel → TopHat /mcp) calls claim_inference_job. The MCP call stays pending (bounded long-poll, capped at 30 s) until a job arrives, then resolves with the job payload (messages + tool defs).

  3. ChatGPT reasons, then calls submit_inference_result(job_id, …). TophatMCP converts the result into exact OpenAI tool_calls / content and resolves the waiting HTTP request.

  4. The loop continues: the agent executes tools locally, sends the next /v1 request → new job → ChatGPT claims → submits.

Both the /v1 handler and the MCP worker tools talk to one shared Job Broker (durable SQLite job state). There is no sync fetch to a WebUI — ChatGPT is reached only through the tunnel-presented MCP server.

Related MCP server: mcp-llm-bridge

Firefox extension worker ownership

The supported browser adapter is a user-operated Firefox extension connected to the actuator through Native Messaging. A toolbar click on a ChatGPT tab is an explicit worker-selection action: it selects that tab/profile as the only browser worker allowed to receive inference prompts. This avoids silently choosing a personal ChatGPT conversation.

More than one Firefox profile may have the extension connected at the same time. The actuator keeps a durable selected-installation record and routes worker.ensure, turn.execute, and turn.stop only to that selected profile. Selecting a worker in another profile explicitly transfers ownership and revokes the prior profile; disconnecting the selected profile leaves the worker offline rather than falling through to a different personal browser. This makes it practical to keep separate work, test, and personal profiles connected without accidentally sending a coding request to the wrong tab.

After the initial selection, TopHat persists both the selected extension installation and the selected conversation URL. It can reopen that thread when the same Firefox profile reconnects. The current implementation is a Linux/Firefox private-beta adapter: do not treat it as a public service or a replacement for an official API. Each user must operate their own logged-in account, connector authorization, and browser session.

Waking a ChatGPT worker

A connector exposes tools to a ChatGPT turn; it does not wake a dormant turn. For unattended use, run a local controller (PTY or browser driver) that long-polls GET /internal/jobs/wait?model=<alias>, sends one exact-job worker prompt when it receives a queued notice, and keeps that browser turn owned until the broker reaches completed, failed, or cancelled. Prompt acceptance is not job completion. The endpoint is loopback-only and can additionally require TOPHAT_INTERNAL_TOKEN.

Each wake prompt tells ChatGPT to claim one exact expected_job_id, so a stale or duplicate wake cannot consume another queued request. A lease is authenticated by worker_id + job_id + lease_token, so claim and submit may use separate transient MCP connections. Browser liveness renews a short lease, always capped by the hard job deadline; a retry is refused when too little deadline budget remains.

The actuator is a separate process: npm run dev:actuator (development) or npm run start:actuator after building. In extension mode it owns queue wakes and retries; the Firefox extension owns narrowly scoped ChatGPT-tab interaction. playwright remains a development fallback. tophat on starts the actuator only in job-broker mode and reports inference as ready only after a browser worker is connected and selected.

MCP worker tools

  • claim_inference_job — out: idle{retry_after_ms} | assigned{job_id, lease_token, lease_expires_at, request}.

  • submit_inference_result — in: job_id, lease_token, result payload.

  • fail_inference_job — in: job_id, lease_token, error.

  • heartbeat_inference_worker — keep-alive.

submit/fail require job_id + lease_token, preventing stale, duplicate, cross-worker, or corrupt submissions (conditional UPDATE … WHERE lease_token_hash=? AND lease_expires_at>? AND deadline_at>? — 0 rows affected = race lost).

Run it

cp .env.example .env
npm install
npm run build          # tsc

Mock backend (in-process scripted worker, no tunnel, no ChatGPT):

TOPHAT_BACKEND=mock npm run dev:http

Real backend (ChatGPT over tunnel):

# set TOPHAT_BACKEND=job-broker (default) and aliases in .env, then:
/home/bamn/bin/tophat on      # boots gateway (tmux: tophat) + tunnel (tmux: tophat-tunnel)

Launch with tophat (one command)

/home/bamn/bin/tophat is a launch controller that boots the stack in tmux and gates the tunnel daemon on gateway readiness:

tophat on      # gateway (tmux: tophat) + OpenAI tunnel (tmux: tophat-tunnel), backend from .env
tophat mock    # gateway in mock mode (no tunnel)
tophat status  # gateway /health, tunnel readiness, ChatGPT discovery check
tophat off     # tears both daemons down
tophat logs    # attach to the gateway tmux session

tophat on prints the inference proxy endpoint + a curl snippet:

=== inference proxy (point your agent here) ===
  base_url: http://127.0.0.1:6767/v1
  curl http://127.0.0.1:6767/v1/chat/completions \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer ${TOPHAT_BEARER_TOKEN}" \
    -d '{"model":"chatgpt-5.6-sol-high","messages":[{"role":"user","content":"hi"}],"stream":false}'

The tunnel daemon needs CONTROL_PLANE_API_KEY (an OpenAI organization runtime key from platform.openai.com/settings/organization/api-keys) in the environment. The tunnel profile (~/.config/tunnel-client/tophat_mcp.yaml) references it via api_key: "env:CONTROL_PLANE_API_KEY" and never contains the credential itself. The key must own (or have access to) the configured tunnel_id, in the same OpenAI organization/project context. For multi-org accounts, also set CONTROL_PLANE_ORGANIZATION_ID. Export it before tophat on:

export CONTROL_PLANE_API_KEY=<org runtime key>
# export CONTROL_PLANE_ORGANIZATION_ID=<org-id>   # only for multi-org accounts
tophat on

The launcher passes the key to the tunnel session through tmux's environment mechanism, never as a command-line argument.

Create/verify the ChatGPT connector at chatgpt.com/#settings/Connectors while the tunnel daemon is running.

OpenAI-compatible surface

{
  "base_url": "http://127.0.0.1:6767/v1",
  "api_key": "<TOPHAT_BEARER_TOKEN, unset in mock mode>",
  "model": "chatgpt-5.6-sol-high"
}

Endpoints:

POST /v1/chat/completions   # stream: true → SSE; false → single JSON
GET  /v1/models
GET  /health
GET  /ready?model=…          # browser-actuator admission readiness

Tool-call round trip (the gateway does not execute tools):

sequenceDiagram
    participant Agent as Coding Agent
    participant Tophat as TophatMCP (Job Broker)
    participant Worker as ChatGPT MCP Worker

    Agent->>Tophat: POST /v1 (tools declared)
    Tophat->>Tophat: enqueue InferenceJob, block HTTP
    Worker->>Tophat: claim_inference_job (pending)
    Tophat-->>Worker: assigned{request + tool defs}
    Worker->>Tophat: submit_inference_result(tool_calls)
    Tophat-->>Agent: HTTP 200 OpenAI tool_calls
    Note over Agent: Executes tools locally
    Agent->>Tophat: POST /v1 (role: tool result)
    Tophat->>Worker: claim resolves with next job
    Worker->>Tophat: submit_inference_result(final text)
    Tophat-->>Agent: HTTP 200 final content

Authentication

Two modes (TOPHAT_AUTH_MODE):

  • oauth (default): MCP SDK OAuth with an Owner-password approval page. The OpenAI surface is gated by TOPHAT_BEARER_TOKEN (constant-time compare).

  • tunnel: OAuth disabled on /mcp; access is delegated to a reverse tunnel and guarded by TOPHAT_TUNNEL_TOKEN. HOST must be a loopback address.

Tests

npm run test:broker runs src/test-job-broker.ts — self-contained broker integration tests covering atomic exact-job claims, deadline-capped leases, hard deadline enforcement, typed waiter rejection, cancellation, tool-call validation, and startup recovery. npm run test:actuator and the extension-driver suites cover browser-turn ownership, bounded wake retries, and extension bridge routing.

Production requirements

  • Keep the HTTP server bound to localhost unless remote access is intentional.

  • Require authentication for remote access.

  • Treat the tunnel token / control-plane key as credentials; never log them.

  • Maintain a separate MCP session per worker connection; idle sessions are reaped.

  • A browser wake remains owned until the authoritative broker job is terminal; do not infer completion from DOM activity or prompt acceptance.

  • Hard job deadlines win over leases. Retries require a configured minimum remaining budget and terminally fail once their wake budget is exhausted.

  • Submit/fail are guarded by lease + deadline; queued/leased rows are recovered to failed(server_restarted) on startup (no durable continuation advertised until the HTTP client has a resume protocol).

F
license - not found
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • A paid remote MCP for OpenAI Codex agent coordination MCP, built to return verdicts, receipts, usage

  • A paid remote MCP for OpenAI Codex memory MCP, built to return verdicts, receipts, usage logs, and a

  • Self-hosted MCP gateway: turn any API, database or MCP server into AI connectors — no code.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/B-A-M-N/TopHatMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server