# Onboarding Guide: connect a local MCP server to ChatGPT or Codex This guide helps you get from zero to a working `tunnel-client` process connected to your MCP server. If you found this by searching "How do I connect local MCP server to ChatGPT", "How do I connect local MCP server to Codex", "localhost to ChatGPT", or "Codex local MCP", start with `tunnel-client help quickstart`. That is the shortest supported path before you drop into `init`, `doctor`, or Codex plugin flows. For the customer-shareable network model and request flow, see [`architecture.md`](architecture.md). For contributor-level connector routing, streaming, and troubleshooting details, see [`connectors.md`](connectors.md). ## 1) Prerequisites - A reachable MCP server endpoint, or a command that starts a stdio MCP server. - A tunnel control-plane API key. - A provisioned `tunnel_id` for your environment. Use these exact setup pages when you need to create or inspect those values: - Tunnels management and supported tunnel-client download: `https://platform.openai.com/settings/organization/tunnels` - 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` Which value comes from where: - `CONTROL_PLANE_TUNNEL_ID`: create or inspect it in Tunnels management, or via `tunnel-client admin tunnels create|list|get ...` with `OPENAI_ADMIN_KEY`. - `CONTROL_PLANE_API_KEY`: create it in Runtime API keys; this is the key used by `tunnel-client doctor` and `tunnel-client run`. - `OPENAI_ADMIN_KEY`: only for `tunnel-client admin tunnels list|create|update|delete`. Do not use the admin key for the long-lived daemon. Required permissions: - The runtime key principal needs Tunnels **Read** + **Use** for the target tunnel. - People who create or edit tunnels need Tunnels **Read** + **Manage**. - People who create `OPENAI_ADMIN_KEY` need Platform admin-key permission as well as any tunnel permissions they need for the task. For the recommended roles/groups and screenshots, see [`permissions.md`](permissions.md). ## 2) First-run paths If you already have a `tunnel-client` binary, start there. The CLI now embeds the shortest first-use path: ```bash tunnel-client help quickstart tunnel-client help samples ``` The recommended binary-first path is: ```bash 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 Codex is installed locally and you want the plugin surface instead of the raw binary flow, install it directly from the binary. The canonical command is `tunnel-client codex plugin ...`; `tunnel-client plugin codex ...` remains a compatibility alias. Keep lifecycle operations on the native `runtimes` command family: ```bash tunnel-client codex plugin install tunnel-client help plugin tunnel-client runtimes list tunnel-client codex plugin uninstall ``` Use `tunnel-client run ...` when you intentionally want a foreground daemon attached to the current terminal. For a long-lived local runtime managed by Codex, prefer `tunnel-client runtimes connect ...`; do not use `nohup` or `disown` as the tunnel-client supervision path. After `runtimes connect`, run `tunnel-client runtimes status ` before reporting success. Only report success when status shows the managed runtime running with health reported; use `--json` when Codex needs explicit `process_running`, `healthy`, and `ready` fields. If you want a named profile instead of the one-command demo path: ```bash 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 ``` If you still need the tunnel id or runtime/admin keys, open the matching setup URL above before running `init`. If your rollout has self-serve tunnel access, create the tunnel yourself in Tunnels management or with `tunnel-client admin tunnels create`, then export the returned id as `CONTROL_PLANE_TUNNEL_ID` and a separate runtime key as `CONTROL_PLANE_API_KEY`. Create or verify the connector from the ChatGPT settings URL above only while `tunnel-client run ...` is healthy, and keep the daemon running for connector discovery and every MCP call from ChatGPT. The Platform Tunnels page download button is sourced from tunnel-service's gated tunnel metadata response. When a new public tunnel-client release becomes the supported download, update tunnel-service's hard-coded public artifact URL alongside the release handoff. Other fast starts: - Remote HTTP MCP server with no OAuth/PRMD metadata: `tunnel-client init --sample sample_mcp_remote_no_auth --profile remote-http --tunnel-id tunnel_... --mcp-server-url https://mcp.example.com/mcp` - Outbound proxy or private PKI environment: `export HTTPS_PROXY="http://proxy.internal.example.com:8080"` `export ENTERPRISE_CA_BUNDLE="/etc/ssl/certs/proxy-root.pem"` then `tunnel-client init --sample sample_mcp_enterprise_proxy --profile corp-proxy --tunnel-id tunnel_... --mcp-server-url https://mcp.internal.example.com/mcp` - Embedded demo MCP server for end-to-end validation: `tunnel-client dev mcp-stub` then `tunnel-client init --sample sample_mcp_with_dcr --profile sample_mcp_with_dcr --tunnel-id tunnel_... --mcp-server-url http://127.0.0.1:NNNN/mcp` The embedded UI is served from the health listener. With the default `127.0.0.1:8080`, the UI is at `http://127.0.0.1:8080/ui`. If you set `HEALTH_LISTEN_ADDR=:0` or `--health.listen-addr=:0`, the OS picks an ephemeral port at startup; use `HEALTH_URL_FILE` or `--health.url-file` if another tool needs the resolved UI URL. If Codex is installed locally and you want the plugin surface instead of the raw binary flow, install it directly from the binary: ```bash 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 codex plugin uninstall ``` Starter prompts for Codex: - `Figure out what tunnel-client is for from the binary help, 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 before reporting whether the runtime is launched, healthy, or ready.` ## 3) Build from source From the `tunnel-client` module root: ```bash make admin-ui go build -o bin/tunnel-client ./cmd/client ``` `make admin-ui` refreshes embedded UI assets before the local binary build. You can skip it for a docs-only checkout when the committed assets under `pkg/adminui/assets` are already present and unchanged. Release source archives include the release semantic version in `pkg/version/VERSION`, so a plain `go build` from a downloaded release `.tar.gz` keeps the same client name and version metadata used by released binaries. After building from source, use `./bin/tunnel-client` unless you add that location to your `PATH`. ## 4) Configure At minimum, you must set: - `CONTROL_PLANE_API_KEY`: control-plane authentication. - `CONTROL_PLANE_TUNNEL_ID`: the tunnel identifier for this deployment. - One `main` MCP binding: - `MCP_SERVER_URL` for a Streamable HTTP MCP endpoint, or - `--mcp.command` for a stdio MCP server. Auth split to keep straight: - `CONTROL_PLANE_API_KEY` / `OPENAI_API_KEY`: runtime key used by the daemon. - `tunnel-client admin tunnels get ` can use that runtime key for read-only metadata lookup. - `admin tunnels list/create/update/delete` require `OPENAI_ADMIN_KEY` or `--admin-key`. - The enterprise proxy sample documents both keys in comments so runtime and admin flows stay separate. Example: ```bash export CONTROL_PLANE_API_KEY="sk-..." # preferred export CONTROL_PLANE_TUNNEL_ID="tunnel_0123456789abcdef0123456789abcdef" export MCP_SERVER_URL="https://mcp.internal.example.com/mcp" ``` `CONTROL_PLANE_TUNNEL_ID` must match the runtime validator: `tunnel_` followed by 32 lowercase hexadecimal characters. For the full surface (flags, defaults, advanced knobs), see [`configuration.md`](configuration.md). ### OAuth-protected MCP (supported) - `Authorization` headers are forwarded through the OpenAI tunnel service to your configured MCP server origin. - Custom MCP request headers configured on the app are forwarded through the OpenAI tunnel service after sanitization, except fixed proxy/hop-by-hop headers (including every header named by `Connection`), internal auth headers, and IP-forwarding transport headers. - Protected-resource OAuth discovery GETs are forwarded to the tunnel-client; discovery payloads and `WWW-Authenticate resource_metadata` are rewritten to OpenAI tunnel-service URLs for the same `tunnel_id`. - `authorization_servers[0]` from PRMD is the only source of truth and metadata fetch target for auth-server metadata enrichment and Harpoon OAuth target registration. - Auth-server metadata is accepted even when metadata `issuer` differs from `authorization_servers[0]` (external IdP issuer topologies are supported), and mismatch diagnostics are retained. - Registered `harpoon://` `registration_endpoint`, `token_endpoint`, and `revocation_endpoint` values are rewritten to Tunnel OAuth-shim routes. Their POST requests and responses traverse Tunnel and Harpoon; public `http(s)` endpoint URLs remain unchanged and are called by the product OAuth caller rather than through Tunnel. - The OAuth shim does not rewrite `authorization_endpoint`; the supported auto-registered path leaves browser authorization direct to the upstream authorization server. Tunnel does not expose arbitrary authorization-server routes. ## 5) Run ```bash ./bin/tunnel-client run --log.level=info --log.format=struct-text ``` This is the foreground/manual operator path. Keep this terminal attached while you want the daemon running. When Codex needs a long-lived local runtime that survives the current shell, use `tunnel-client runtimes connect ...` instead of improvising `nohup` or `disown`. The process will: - Start polling the OpenAI tunnel service for work. - Forward JSON-RPC requests to your MCP server. - Expose health endpoints on `HEALTH_LISTEN_ADDR` (default `127.0.0.1:8080`). Set it to `:8080` only when a trusted remote probe needs access, or `127.0.0.1:0` when you explicitly want an ephemeral loopback port chosen at startup. ## 6) Verify In another shell: ```bash curl -fsS "http://127.0.0.1:8080/healthz" curl -fsS "http://127.0.0.1:8080/readyz" curl -fsS "http://127.0.0.1:8080/metrics" | head ``` ## 7) Next reads - **Permissions, roles, and groups**: [`permissions.md`](permissions.md) - **Deployments**: [`deployment/overview.md`](deployment/overview.md) - **Architecture**: [`architecture.md`](architecture.md) - **Troubleshooting**: [`troubleshooting.md`](troubleshooting.md)