openai/tunnel-client: connect a private MCP server to ChatGPT, Codex and the Responses API
Customer-run client for Secure MCP Tunnel: connect private or localhost MCP servers to ChatGPT, Codex, the Responses API, and AgentKit without exposing them to the public internet.
At a glance
- What is it?
- tunnel-client is the customer-run daemon behind Secure MCP Tunnel. It keeps an MCP server off the public internet and still lets OpenAI-hosted products reach it. The trade-off is that you now operate a network component, and the docs are still catching up with the code.
- Who is it for?
- Adopt tunnel-client if your MCP server sits on a laptop, a VM or inside a cluster and security will not approve an inbound rule, and if you can run a daemon with /healthz, /readyz and /metrics. Do not adopt it if you only need a public MCP endpoint, or if you cannot own the supervision path: the README is explicit that nohup and disown are not the tunnel-client supervision route, and that runtimes connect plus runtimes status is the supported one.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem tunnel-client solves: a private MCP server that ChatGPT still has to reach
An MCP server running on a laptop, a VM, a Kubernetes cluster or a private network is unreachable from an OpenAI-hosted product. The usual fix is an inbound firewall rule and a public endpoint, which is exactly what security teams tend to reject. tunnel-client is the customer-run half of Secure MCP Tunnel: it dials out to an OpenAI-hosted tunnel endpoint and carries MCP traffic over that connection, so the server itself stays off the public internet.
The README lists three situations where this is the right tool. You have an MCP server somewhere private and need ChatGPT, Codex, the Responses API or AgentKit to reach it. Security will not approve a new inbound rule. You want an operator-visible daemon with /healthz, /readyz, /metrics and /ui before a connector or API call depends on it. That third point matters more than it looks: the project assumes someone will watch the process, not just start it once.
The audience is therefore narrower than "anyone using MCP". It is platform or infrastructure engineers who already run Go or container workloads and are willing to own a long-lived network daemon. If your MCP server is already publicly reachable, tunnel-client adds a component without removing a problem.
How the tunnel works: a customer-run agent in front of an OpenAI-hosted endpoint
The README describes tunnel-client as "the customer-run agent behind Secure MCP Tunnel". The client runs on your side, connects outbound to the OpenAI-hosted MCP tunnel endpoint, and the private MCP server is reached through that connection rather than directly. The trust-boundary and network story is written up in docs/architecture.md, which is the document the README points at when you need something you can share with a reviewer.
The repository layout confirms this is more than a thin proxy. There is a cmd/ tree, a pkg/ tree, an adminui/ directory whose assets are built into pkg/adminui/assets, and separate runtime binaries: the Makefile defines tunnel-client, tunnel-client-runtime and tunnel-client-runtime-cloudflared as distinct targets from ./cmd/client, ./cmd/client-runtime and ./cmd/client-runtime-cloudflared. A bundled cloudflared companion is documented in docs/deployment/cloudflared.md, and the Homebrew formula installs it alongside the client.
The Go SDK path skips the network entirely. If your MCP server is written in Go, you can run it in the same process and hand the server side of an in-memory MCP transport pair to your server and the client side to tunnelclient.New. No port, no stdio. For other languages, the repository ships proxy examples in examples/python-mcp-tunnel-client-proxy and examples/typescript-mcp-tunnel-client-proxy, and docs/protocol.md plus docs/openapi.json define the wire contract if you want to build a client yourself.
Installing tunnel-client and getting a first run to a healthy state
On macOS, Homebrew is the supported installation path. The README warns that directly downloaded release ZIPs are not currently notarized and can be blocked by Gatekeeper, and it explicitly says not to use xattr, spctl or Open Anyway to bypass that check. Install from the OpenAI tap instead:
brew install openai/tools/tunnel-clientThe Formula installs the matching tunnel-client, the bundled cloudflared and the companion manifest together, while exposing only the tunnel-client command. Confirm what you got, then read the guided setup before configuring anything:
tunnel-client --version
tunnel-client help quickstartFor the shortest working path from a localhost or private MCP server to ChatGPT or Codex, the README points at docs/onboarding.md. Two values have to come from the OpenAI platform rather than from the client itself: CONTROL_PLANE_TUNNEL_ID, which you create or inspect in Tunnels management or via tunnel-client admin tunnels create|list|get with OPENAI_ADMIN_KEY, and the runtime API key from the organization API keys page. The setup pages the README names are the tunnels page, the organization roles and groups pages, the runtime and admin API key pages, and the ChatGPT connector settings.
For a long-lived local runtime, the README says to prefer the managed runtime rather than a foreground daemon, and to check it before declaring success:
tunnel-client runtimes connect ...
tunnel-client runtimes status <alias>Only report success when status shows the managed runtime running with health reported. Use --json when an agent such as Codex needs the explicit process_running, healthy and ready fields. Docker, Kubernetes and VM deployments are covered separately in docs/deployment/overview.md.
Supervision is the sharp edge, and the README says so
The clearest limitation is not in the protocol, it is in process management. The README draws a hard line: use tunnel-client run ... when you intentionally want a foreground daemon attached to the current terminal, and for a long-lived runtime managed by Codex prefer tunnel-client runtimes connect .... It then states that nohup and disown are not the tunnel-client supervision path. Anyone who treats this like a typical background daemon and reaches for nohup will be outside the supported route.
That has a knock-on effect on automation. The success signal is not the exit code of a start command; it is the state reported by tunnel-client runtimes status <alias>, or the process_running, healthy and ready fields under --json. A deployment script that starts the runtime and moves on has no way to know whether the tunnel is actually usable.
There is a second boundary worth naming. The README describes the client as connecting to an OpenAI-hosted tunnel endpoint. Nothing in the README suggests tunnel-client is a general-purpose tunnel for arbitrary traffic, or a replacement for a VPN. If you want a generic tunnel to an internal HTTP service, this is the wrong tool. It is also the wrong tool if you only need an MCP server that is already publicly reachable, since the tunnel adds a component without removing an inbound rule.
The documentation set is broad but the project is young. The latest release is v0.0.14, published on 2026-09-01, after v0.0.13 on 2026-08-26 and a v0.0.13-dev build on 2026-08-20. Pre-1.0 version numbers plus a command tree that includes runtimes, admin-profiles and plugins mean the CLI surface is still moving. Pin a version and read docs/configuration.md rather than assuming a flag from a blog post still exists.
tunnel-client compared with SSH tunnels and Cloudflare Tunnel
The nearest thing most engineers already have is an SSH remote forward. It also avoids an inbound firewall rule, and it also gives you a connection from a private host to something outside. The difference is what sits at the far end. An SSH forward terminates on a host you control and carries raw TCP; tunnel-client terminates on an OpenAI-hosted MCP tunnel endpoint and speaks MCP through it, which is why the README can point at docs/protocol.md and docs/openapi.json as the contract for other client implementations. With SSH you also own key distribution, host key verification and the forwarding process on the remote side. tunnel-client replaces that with tunnel IDs, runtime API keys and admin API keys, documented in docs/permissions.md.
Cloudflare Tunnel is the other obvious comparison, and here the relationship is unusual: tunnel-client bundles a cloudflared companion, documented in docs/deployment/cloudflared.md and installed by the Homebrew formula. So the choice is not tunnel-client or cloudflared in the abstract. It is whether you want an MCP-aware client with connector behavior, health endpoints and a UI, or a general-purpose tunnel you wire up yourself. If your traffic is not MCP, a general-purpose tunnel is the better fit.
A third option is no tunnel at all: expose the MCP server directly. That is simpler and has fewer moving parts, and it is the correct answer when the server is meant to be public. tunnel-client exists for the case where it is not.
Maintenance, licensing and the cost of running a Go daemon
The licence is Apache-2.0, which is permissive and includes an explicit patent grant. That is a normal choice for infrastructure code and it does not restrict commercial use. It also means there is no copyleft obligation on your own code. This is a description of the licence identifier in the repository, not legal advice; if your organization has a policy on Apache-2.0 dependencies, run it through the usual review.
Operationally, the cost is a long-lived process that has to be versioned, supervised and monitored. The binary exposes /healthz, /readyz, /metrics and /ui, and the Go module pulls in OpenTelemetry and the Prometheus client, so there is a metrics surface to scrape rather than something you have to build. There is also a bundled admin UI built from adminui/ into pkg/adminui/assets, which means the release artifact contains front-end assets and the build pulls pnpm and Node 22 alongside Go. Building from source is heavier than a single go build, though Homebrew and the Dockerfiles avoid that for most users.
Upgrade cost is the real question. With v0.0.14 as the latest release and releases roughly weekly through late August and early September 2026, you should expect to move versions rather than sit still. The README's own instruction to verify with tunnel-client runtimes status <alias> after connecting applies equally after an upgrade: the health and readiness signals are how you confirm the new binary is doing its job.
Embedding tunnel-client in a Go process instead of running a daemon
The module is importable, and this path removes the network hop between your server and the client. The README gives the module path and an example that pairs an in-memory MCP transport with tunnelclient.New:
go get github.com/openai/tunnel-clientimport (
"context"
"github.com/modelcontextprotocol/go-sdk/mcp"
tunnelclient "github.com/openai/tunnel-client"
)
ctx := context.Background()
server := mcp.NewServer(&mcp.Implementation{Name: "my-server", Version: "1.0.0"}, nil)
serverTransport, tunnelTransport := mcp.NewInMemoryTransports()
go server.Run(ctx, serverTransport)
client, err := tunnelclient.New(tunnelclient.Config{
TunnelID: "tunnel_0123456789abcdef0123456789abcdef",
APIKey: apiKey,
}, tunnelTransport)
if err != nil {
return err
}
return client.Run(ctx)The runnable version lives in examples/go-sdk-inmemory, which registers an echo tool and connects it to the OpenAI Tunnel control plane. The trade-off is real: you give up the operator-visible daemon with its health endpoints and UI, because there is no separate process to probe. That is a good exchange for a Go server that ships as one binary, and a bad one if your operations team expects a process to monitor.
Editorial conclusion
Adopt tunnel-client if your MCP server sits on a laptop, a VM or inside a cluster and security will not approve an inbound rule, and if you can run a daemon with /healthz, /readyz and /metrics. Do not adopt it if you only need a public MCP endpoint, or if you cannot own the supervision path: the README is explicit that nohup and disown are not the tunnel-client supervision route, and that runtimes connect plus runtimes status is the supported one. Before rolling it out, verify the tunnel ID and API key scopes against docs/permissions.md and confirm the Go module version you pin, since the latest release is v0.0.14.
Frequently asked questions
What is openai/tunnel-client used for?
It connects a private or localhost MCP server to ChatGPT, Codex, the Responses API and AgentKit through an OpenAI-hosted MCP tunnel endpoint, keeping the MCP server off the public internet. The README lists it as the tool to use when security will not approve a new inbound firewall rule or public endpoint.
How do I install tunnel-client on macOS?
Homebrew is the supported installation path: brew install openai/tools/tunnel-client. The README warns that directly downloaded release ZIPs are not currently notarized and can be blocked by Gatekeeper, and says not to use xattr, spctl or Open Anyway to bypass that check.
Where does the tunnel client get its tunnel ID and API key?
The README says CONTROL_PLANE_TUNNEL_ID is created or inspected in Tunnels management, or via tunnel-client admin tunnels create|list|get with OPENAI_ADMIN_KEY. Runtime API keys come from the organization API keys page, and admin API keys from the admin keys page.
Does tunnel-client work with Codex?
Yes. The README points at tunnel-client help quickstart for the shortest path from a local or localhost MCP server to ChatGPT or Codex, and says Codex plugin lifecycle work uses the tunnel-client runtimes and tunnel-client admin-profiles command trees.
Can I run tunnel-client in the background with nohup?
The README states that nohup and disown are not the tunnel-client supervision path. For a long-lived local runtime managed by Codex it says to prefer tunnel-client runtimes connect, then confirm with tunnel-client runtimes status <alias> before reporting success.
Can I embed tunnel-client in my own Go MCP server?
Yes. The module is github.com/openai/tunnel-client, and the README shows passing the client side of an in-memory MCP transport pair to tunnelclient.New with a TunnelID and APIKey. A runnable version is in examples/go-sdk-inmemory.
Community notes