OpenAI tunnel-client
Connect a local or private MCP server to ChatGPT and Codex without exposing it to the public internet.
This guide is the shortest complete operator path from "I have a private MCP server" to "ChatGPT and Codex can reach it through a tunnel." It keeps the control-plane values, local runtime steps, and product setup screens in one place so you do not need to stitch together four different docs or a Slack thread first.
What you need
- A private or local MCP server that tunnel-client can reach.
- A
tunnel_idfrom OpenAI Platform Tunnels management. - A supported
tunnel-clientbinary from Platform Tunnels management or the latest public release. - A runtime API key for the long-lived daemon.
- An admin key only if you will create, list, update, or delete tunnels from the CLI.
What usually blocks operators
tunnel_id is not the same thing as the runtime API key, and a tunnel showing up in Platform does not automatically mean it will appear in ChatGPT. This guide calls out those boundaries directly.
tunnel-client is the customer-run process that keeps an outbound-only HTTPS connection open to
the OpenAI tunnel control plane. It receives work for one tunnel, forwards that work to your MCP
server, and exposes local operator surfaces at /healthz, /readyz, /metrics, and /ui.
If you want the shortest local discovery path first, run:
tunnel-client help quickstartUse these exact setup pages when you need to create or inspect values:
- Tunnels management and supported tunnel-client download:
https://platform.openai.com/settings/organization/tunnels - Latest public tunnel-client release:
https://github.com/openai/tunnel-client/releases/latest - Organization roles:
https://platform.openai.com/settings/organization/people/roles - Organization groups:
https://platform.openai.com/settings/organization/people/groups - Runtime API keys:
https://platform.openai.com/settings/organization/api-keys - Admin API keys:
https://platform.openai.com/settings/organization/admin-keys - ChatGPT connector settings:
https://chatgpt.com/#settings/Connectors
Keep these three values straight:
CONTROL_PLANE_TUNNEL_ID
Where you get it
Platform Tunnels management, or tunnel-client admin tunnels create|list|get ....
What it is for
Identifies the tunnel object that ChatGPT and tunnel-client must both use.
When you need it
Always.
CONTROL_PLANE_API_KEY
Where you get it
Platform Runtime API keys.
What it is for
Authenticates tunnel-client doctor and tunnel-client run.
When you need it
Always.
OPENAI_ADMIN_KEY
Where you get it
Platform Admin API keys.
What it is for
Authenticates tunnel-client admin tunnels list|create|update|delete.
When you need it
Only for tunnel CRUD.
The permission split is equally important:
- Runtime users need Tunnels Read + Use.
- Tunnel managers need Tunnels Read + Manage.
- People who create admin keys need the Platform admin-key permission in addition to any tunnel permissions they need.
When creating CONTROL_PLANE_API_KEY in Platform Runtime API keys, choose Restricted and select Tunnels Read + Use. Do not use All or swap in an admin key for the long-lived daemon.
Reused checked-in capture: Platform > Organization roles > Tunnels permissions.
Reused checked-in capture: Platform > Organization groups > assign the tunnel role to the right operator group.
If you are creating roles and groups from scratch:
- Create a runtime-user role with Tunnels Read + Use.
- Create a manager role with Tunnels Read + Manage, plus Use if the same people also run the daemon or configure the connector.
- Assign those roles to groups instead of editing people one by one.
- After the role assignment is in place, create new runtime or admin keys if practical, then rerun
tunnel-client doctor --explain.
The tunnel itself is the shared anchor between Platform, ChatGPT, and your local runtime. You can create it from the Platform Tunnels page or with the admin-key-backed CLI path:
tunnel-client admin tunnels create \
--name "Production MCP Tunnel" \
--description "Routes ChatGPT connector traffic to the production MCP server" \
--organization-id <ORG_ID> \
--workspace-id <WORKSPACE_ID>Two details matter here:
- The runtime daemon and the ChatGPT connector must use the same
tunnel_id. - If the tunnel should appear in a ChatGPT workspace picker, create it with the correct workspace scope. A tunnel can exist in Platform and still not appear in ChatGPT if the workspace wiring or connector permissions are wrong.
Use the Platform UI when you want the cleanest self-serve path. Use the admin CLI path when you
already have OPENAI_ADMIN_KEY and need repeatable create, list, or update operations.
The shortest supported binary-first path is to let the CLI explain itself before you hand-edit configuration:
tunnel-client help quickstart
tunnel-client help doctor
tunnel-client help pluginIf you already have a tunnel ID and want the smallest end-to-end demo:
export CONTROL_PLANE_API_KEY="sk-..."
health_url_file="$(mktemp "${TMPDIR:-/tmp}/tunnel-client-health.XXXXXX.url")"
tunnel-client run \
--embedded-mcp-stub \
--control-plane.tunnel-id tunnel_0123456789abcdef0123456789abcdef \
--health.listen-addr 127.0.0.1:0 \
--health.url-file "$health_url_file"
curl -fsS "$(cat "$health_url_file")/readyz"
open "$(cat "$health_url_file")/ui"If you want a named profile instead of the one-command demo path:
tunnel-client init \
--sample sample_mcp_stdio_local \
--profile local-stdio \
--tunnel-id tunnel_0123456789abcdef0123456789abcdef \
--mcp-command "python /path/to/server.py"
tunnel-client doctor --profile local-stdio --explain
tunnel-client run --profile local-stdioWhat to look for:
/healthzreturns HTTP 200 when the process is alive./readyzreturns HTTP 200 when the existing startup readiness policy allows work. The stdio startup probe skips protocol discovery, so this can be ready before the child has answered initialize or tools/list./health/mcpreports discovery actually observed from the main stdio child;/health?details=truealso explains polling, uploads, queues, and active work. Reads never initiate discovery. See component health./uigives you the local operator dashboard.
If doctor --explain says the runtime key is missing, fix CONTROL_PLANE_API_KEY.
If Platform knows the tunnel but ChatGPT does not, fix the workspace or connector permissions before
you assume the daemon is wrong.
The local UI is where you confirm the runtime is really alive, not just launched. The screenshots below were captured from live local runs, with the Overview tab refreshed on May 21, 2026.
Overview: health, readiness, tunnel, and MCP status in one place.
Metrics: quick read of the exported Prometheus counters from /metrics.
Logs: live stream, filtering, and support-bundle export.
Assistant: Codex status, login state, and bridge activity from the same local runtime.
When you are checking a local run, use this order:
- Open
/readyz. If it is not ready, the connector will not be reliable yet. - Open
/ui#overviewto confirm the active tunnel and MCP target. - Open
/ui#metricswhen you want a quick read on request volume or readiness counters. - Open
/ui#logswhen you need the real error message instead of guessing from symptoms. - Open
/ui#codexwhen you are validating the Codex bridge, login state, or plugin setup.
Once the local runtime is healthy, open https://chatgpt.com/#settings/Connectors and choose
Connection: Tunnel. Then select the tunnel or paste the tunnel_id.
Leave tunnel-client run ... running while you do this. The daemon must stay up for connector
discovery and every later MCP tool call.
tunnel-client run ... is the foreground/manual operator path. If Codex is asked to start a
long-lived local runtime, use tunnel-client runtimes connect ... instead of nohup or disown.
If the tunnel does not appear in ChatGPT:
- Confirm the tunnel was created with the correct workspace scope.
- Confirm the connector operator has Tunnels Read + Use.
- Confirm the daemon is still healthy and
/readyzis passing. - Confirm the tunnel is not so new that the control plane is still propagating it.
You have two supported Codex paths:
tunnel-client codex assistant ...for the shortest terminal assistant bridge.tunnel-client codex plugin installplus the nativetunnel-client runtimes ...andtunnel-client admin-profiles ...commands when you want the persistent local plugin surface.
Useful commands:
tunnel-client codex assistant "Summarize what tunnel-client is for."
tunnel-client codex status
tunnel-client codex plugin install
tunnel-client runtimes list
tunnel-client runtimes status <alias>
tunnel-client admin-profiles listIf you are attaching to an existing tunnel instead of creating one:
tunnel-client runtimes connect \
--alias prod-mcp \
--tunnel-id tunnel_0123456789abcdef0123456789abcdef \
--runtime-api-key env:CONTROL_PLANE_API_KEY \
--mcp-server-url https://mcp.example.com/mcpAfter runtimes connect, run tunnel-client runtimes status <alias> before reporting success.
Only report success after status reports the managed runtime running with health surfaced; use
--json when Codex needs explicit process_running, healthy, and ready fields.
Copy these exactly when you want Codex to take the first operator steps for you:
Figure out what tunnel-client is for from the binary help, then get me to /ui with the shortest local path.I only have the source checkout. Figure out how to build tunnel-client, then get me to /ui with the shortest local path.Use tunnel-client to create or reuse a profile, run doctor --explain, and then start the foreground daemon attached to this terminal.Run tunnel-client codex assistant and summarize what this checkout is for in one sentence.Install the Codex plugin from the tunnel-client binary, connect the provided tunnel id, and tell me whether the runtime is launched, healthy, or ready.For a long-lived local runtime, use tunnel-client runtimes connect to attach the provided tunnel id, then run tunnel-client runtimes status <alias> before reporting whether the runtime is launched, healthy, or ready.
From Platform Tunnels management, or from tunnel-client admin tunnels create|list|get ... if you
already have OPENAI_ADMIN_KEY.
From Platform Runtime API keys. This is the key that tunnel-client doctor and tunnel-client run
expect.
Only when you are creating, listing, updating, or deleting tunnels through the admin CLI. Do not swap the admin key in for the long-lived daemon.
Usually one of three reasons: the tunnel was created without the correct workspace scope, the connector operator does not have Tunnels Use, or the local daemon is not running and ready.
Check /readyz first. Then open /ui#overview and /ui#logs. A launched process is not enough;
the runtime needs to be ready.
Use Platform when you want the clearest self-serve operator path. Use the admin CLI when you need a repeatable scriptable flow and you already have the admin key.
Use this guide for the main operator journey, then fall through to the deeper references when you need them:
permissions.mdfor the complete roles and groups walkthroughonboarding.mdfor the broader CLI-first startup pathsconfiguration.mdfor the full runtime, logs, metrics, and assistant surfaceconnectors.mdfor connector transport and auth behaviorenterprise-customer-onboarding.mdfor the customer-shareable architecture explanation


