Skip to content

Latest commit

 

History

History
353 lines (275 loc) · 15.9 KB

File metadata and controls

353 lines (275 loc) · 15.9 KB

Tunnel End-User Guide

OpenAI tunnel-client

Connect a local or private MCP server to ChatGPT and Codex without exposing it to the public internet.

Permissions and groups Tunnel creation Local /ui checks ChatGPT connector Codex workflows
Abstract illustration of a secure tunnel path between operator surfaces and a private MCP server.

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_id from OpenAI Platform Tunnels management.
  • A supported tunnel-client binary 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.

What tunnel-client does

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 quickstart

Before you start

Abstract divider showing keys, roles, and a tunnel identifier.

Use 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.

Platform roles screen with Tunnels permissions. Reused checked-in capture: Platform > Organization roles > Tunnels permissions. Platform group role assignment for a tunnel group. 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:

  1. Create a runtime-user role with Tunnels Read + Use.
  2. Create a manager role with Tunnels Read + Manage, plus Use if the same people also run the daemon or configure the connector.
  3. Assign those roles to groups instead of editing people one by one.
  4. After the role assignment is in place, create new runtime or admin keys if practical, then rerun tunnel-client doctor --explain.

Create the tunnel

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>

Platform create tunnel modal.

Reused checked-in capture: Platform > Tunnels > Create tunnel modal.

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.

Get to first success in the terminal

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 plugin

If 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-stdio

What to look for:

  • /healthz returns HTTP 200 when the process is alive.
  • /readyz returns 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/mcp reports discovery actually observed from the main stdio child; /health?details=true also explains polling, uploads, queues, and active work. Reads never initiate discovery. See component health.
  • /ui gives 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.

Check the local UI

Abstract divider showing a local dashboard, charts, and runtime status cards.

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.

Tunnel client Overview tab. Overview: health, readiness, tunnel, and MCP status in one place. Tunnel client Metrics tab. Metrics: quick read of the exported Prometheus counters from /metrics. Tunnel client Logs tab. Logs: live stream, filtering, and support-bundle export. Tunnel client Assistant tab. Assistant: Codex status, login state, and bridge activity from the same local runtime.

When you are checking a local run, use this order:

  1. Open /readyz. If it is not ready, the connector will not be reliable yet.
  2. Open /ui#overview to confirm the active tunnel and MCP target.
  3. Open /ui#metrics when you want a quick read on request volume or readiness counters.
  4. Open /ui#logs when you need the real error message instead of guessing from symptoms.
  5. Open /ui#codex when you are validating the Codex bridge, login state, or plugin setup.

Warning dialog before downloading recent logs from the admin UI.

Reused checked-in capture: local Logs tab export confirmation. Use this when you need a redacted support bundle for debugging.

Connect ChatGPT

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.

ChatGPT connector settings showing Connection: Tunnel.

Reused checked-in capture: ChatGPT > Settings > Connectors > Connection: Tunnel.

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 /readyz is passing.
  • Confirm the tunnel is not so new that the control plane is still propagating it.

Use it from Codex

Abstract divider showing a prompt window, tunnel path, and operator actions.

You have two supported Codex paths:

  • tunnel-client codex assistant ... for the shortest terminal assistant bridge.
  • tunnel-client codex plugin install plus the native tunnel-client runtimes ... and tunnel-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 list

If 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/mcp

After 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.

Starter phrases for Codex

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.

FAQ

Where do I get CONTROL_PLANE_TUNNEL_ID?

From Platform Tunnels management, or from tunnel-client admin tunnels create|list|get ... if you already have OPENAI_ADMIN_KEY.

Where do I get CONTROL_PLANE_API_KEY?

From Platform Runtime API keys. This is the key that tunnel-client doctor and tunnel-client run expect.

When do I need OPENAI_ADMIN_KEY?

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.

Why can the tunnel exist in Platform but still not appear in ChatGPT?

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.

How do I tell whether the local runtime is healthy enough for ChatGPT or Codex?

Check /readyz first. Then open /ui#overview and /ui#logs. A launched process is not enough; the runtime needs to be ready.

Should I use Platform or the admin CLI to create the tunnel?

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.

Companion docs

Use this guide for the main operator journey, then fall through to the deeper references when you need them: