openai/tunnel-client

▲ 10 stars today★ 438⑂ 87

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.

About openai/tunnel-client

openai/tunnel-client is an open-source project on GitHub, mainly written in Go. Customer-run client for Secure MCP Tunnel: connect private or localhost MCP servers to ChatGPT, Codex, the Responses API It currently holds 438 stars and 87 forks with 0 open issues, and was last pushed on an unknown date (repository created unknown).

Project Overview

AI Homed tracks it on the Today's Trending board, currently at rank #55 with 10 new stars today.

GitHub Repository Details

Repository openai/tunnel-client · default branch - · size 0 KB · watchers 0 · source: GitHub REST API and repository README

README

Secure MCP Tunnel client

tunnel-client is the customer-run agent behind Secure MCP Tunnel. It connects a private or localhost MCP (Model Context Protocol) server to ChatGPT, Codex, the Responses API, and AgentKit through an OpenAI-hosted MCP tunnel endpoint, while keeping the MCP server off the public internet.

Use it when:

network and need an OpenAI-hosted product to reach it. the MCP server. and /ui before a connector or API call depends on it.

If you searched for "secure MCP tunnel", "MCP tunnel ChatGPT", "connect local MCP server to ChatGPT", "connect local MCP server to Codex", "localhost to ChatGPT", or "Codex local MCP", start with tunnel-client help quickstart, then read the onboarding guide below.

Start Here

ChatGPT or Codex? Start with docs/onboarding.md. docs/architecture.md. docs/permissions.md. docs/deployment/overview.md. docs/troubleshooting.md. docs/protocol.md and use docs/openapi.json. Optional client features use the common X-Tunnel-Client-Capabilities header. routing correction and activation notes. Supporting clients work with existing services immediately; corrections are enabled separately after client release. No configuration change is needed. the MCP SDK's in-memory transport; see examples/go-sdk-inmemory.

Try the embedded demo

With a runtime API key and tunnel ID, run the built-in server_info, echo, and uppercase tools without a separate MCP server:

export CONTROL_PLANE_API_KEY="sk-..."
export CONTROL_PLANE_TUNNEL_ID="tunnel_0123456789abcdef0123456789abcdef"
tunnel-client run --embedded-stateless-mcp-stub --health.listen-addr 127.0.0.1:0

--embedded-stateless-mcp-stub uses stateless MCP handling even when a client sends initialize and notifications/initialized. It issues no MCP session ID, and these demo tools do not require MCP session affinity between processes. OAuth and application state have separate requirements.

--embedded-mcp-stub keeps its existing compatibility behavior: legacy initialization and session requests use stateful handling; self-contained modern discovery and tool requests use stateless handling. Choose one embedded mode per run. Both share the embedded listen-address, Unix-socket, server-name, and server-version options; see embedded demo configuration for defaults and target conflicts.

Embed as a Go SDK

The module can run in the same process as a Go MCP server. The MCP server does not need to bind a port or use stdio: give the server side of an in-memory MCP transport pair to your server and the client side to tunnelclient.New.

~~~bash go get github.com/openai/tunnel-client ~~~

~~~go import ( "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 Go SDK example registers an echo tool and connects it to the OpenAI Tunnel control plane.

Documentation Map

developers.openai.com/api/docs/guides/secure-mcp-tunnels docs/enterprise-customer-onboarding.md docs/deployment/cloudflared.md examples/go-sdk-inmemory

Install with Homebrew

On macOS, Homebrew is the supported installation path. Directly downloaded release ZIPs are not currently notarized and can be blocked by Gatekeeper. If a manually downloaded archive is blocked, do not use xattr, spctl, or Open Anyway to bypass the check; install from the official OpenAI tap instead:

brew install openai/tools/tunnel-client

Verify the installed version, then start with the guided setup:

tunnel-client --version
tunnel-client help quickstart

The Formula installs the matching tunnel-client, bundled cloudflared, and companion manifest together, while exposing only the tunnel-client command. For Docker, Kubernetes, or VM deployments, see docs/deployment/overview.md.

To generate the shareable guide output locally:

make end-user-guide-screenshots
make end-user-guide-html
make end-user-guide-slides

For Codex / Copilot

If you want the shortest supported path from a local or localhost MCP server to ChatGPT or Codex, start with tunnel-client help quickstart. For Codex plugin lifecycle work, use the native tunnel-client runtimes ... and tunnel-client admin-profiles ... command trees surfaced by tunnel-client help plugin.

Supervision choice:

attached to the current terminal. tunnel-client runtimes connect .... Do not use nohup or disown as the tunnel-client supervision path. before reporting success. Only report success when status shows the managed runtime running with health reported. Use --json when Codex needs the explicit process_running, healthy, and ready fields.

Use these exact setup pages during first use:

https://platform.openai.com/settings/organization/tunnels Which value comes from where: tunnel-client admin tunnels create|list|get ... with OPENAI_ADMIN_KEY. by tunnel-client doctor and tunnel-client run. list|create|update|delete`. Do not use the admin key for the long-lived daemon.

Required tunnel permissions:

Tunnels Read + Use. run the daemon or attach ChatGPT connectors. tunnel permissions they need.

See docs/permissions.md for the group/role workflow and screenshots.

Binary-first flow:

tunnel-client help quickstart
tunnel-client profiles samples list
tunnel-client profiles samples show sample_mcp_enterprise_proxy
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
tunnel-client run --profile-file ./profiles/local-stdio.yaml

Stdio deployment limit: run only one active tunnel-client instance per tunnel ID when using --mcp.command / MCP_COMMAND. Multiple active instances sharing that tunnel ID are not supported, including overlap during a restart. Each instance launches a separate MCP child, and initialization and later requests can reach different children. Stop the old instance before starting its replacement, or use distinct tunnel IDs for independent instances. See stdio deployment limits.

Stdio initialization is checked automatically. Legacy calls require a successful initialize exchange followed by notifications/initialized; premature calls return mcp_initialization_required immediately. Self-contained MCP requests using protocol version 2026-07-28 or later pass through without a handshake. The caller owns initialization, including after child replacement. See configuration for lifecycle behavior.

If you need the tunnel id or runtime/admin keys first, open the matching 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.

Validate a source checkout with native Go tooling:

go build ./...
go test ./...

Independent tests, subtests, and fuzz seed cases use t.Parallel(). Give each case its own mutable fixtures, and use t.Cleanup for resources shared by parallel children. Keep dependent state transitions in one test. Keep tests that mutate process-wide globals, environment variables, signals, or stdio serial.

Check concurrent execution with the race detector and shuffled test order:

go test -race -shuffle=on -count=3 ./...

The E2E and mock-server packages default to two concurrent tests to bound resource use. An explicit -parallel=N overrides that default.

SBOMs

The public repository includes deterministic six-platform dependency baselines for the full client, runtime, and runtime with Cloudflared. They inventory synthetic payloads built from declared offline source and vendor snapshots, including pinned Cloudflared module versions, purls, and CPEs. Do not hand-edit them; maintainers refresh them through the hermetic SBOM generation check when dependency inputs or a Cloudflared pin changes. The baselines are useful for dependency review and drift detection, but they do not claim that a public release ZIP contains the same bytes.

From a checkout of this public repository, verify that the mirrored baseline files match their mirrored manifest before importing them into a dependency scanner:

./scripts/verify_sbom_baselines.sh

That command proves the checkout's three baseline documents match compliance/sbom-baseline-manifest.json and parse as SPDX 2.3. It does not prove that any release archive contains those bytes.

Validate a downloaded release

Releases produced by the current release workflow publish a matching SPDX 2.3 sidecar for each ZIP, embed the same sidecar in the ZIP, cover both files in SHA256SUMS.txt, and publish the workflow's signed Sigstore provenance bundle. They also publish:

checksum-pinned Grype binary from exactly 18 release-specific SPDX sidecars: three flavors across six platforms, each bound to its matching ZIP and license-report SHA256, with the exact vulnerability-database SHA256 recorded; every automated statement set to under_investigation rather than claiming that a finding is fixed, exploitable, or not affected; and release evidence bytes, scanner/database identity, scan scope, and provenance boundary.

The vulnerability report explicitly records OCI images as not_scanned: the release contract has multi-architecture OCI index digests, not exact per-platform OCI manifest scan inputs. Every pre-bundle release artifact is covered by SHA256SUMS.txt, PUBLIC_URLS.txt, and the signed provenance bundle. The bundle is intentionally excluded from its own checksum and signature subject set. Older releases without .spdx.json sidecars cannot be archive/SBOM-validated this way; releases without a *-provenance.sigstore.json bundle cannot be provenance-validated this way. Use the release sidecar, not a checked-in baseline, to validate downloaded bytes.

From a checkout of this public repository at the matching release tag, replace the example tag and choose one of client, runtime, or runtime-cloudflared. The commands require Bash, Python 3, curl, and the GitHub CLI:

release=vX.Y.Z
platform=linux-amd64
flavor=runtime
prefix=tunnel-client-runtime
stem="${prefix}-${release}-${platform}"
base="https://github.com/openai/tunnel-client/releases/download/${release}"
bundle="tunnel-client-${release}-provenance.sigstore.json"
source_digest="$(git rev-parse HEAD)"

curl -fLO "${base}/${stem}.zip" curl -fLO "${base}/${stem}.spdx.json" curl -fLO "${base}/${stem}-licenses.txt" curl -fLO "${base}/SHA256SUMS.txt" curl -fLO "${base}/${bundle}"

gh attestation verify "${stem}.zip" \ --bundle "${bundle}" \ --repo openai/tunnel-client \ --signer-workflow openai/tunnel-client/.github/workflows/release.yml \ --source-ref "refs/tags/${release}" \ --source-digest "${source_digest}" \ --signer-digest "${source_digest}" \ --predicate-type https://slsa.dev/provenance/v1 \ --deny-self-hosted-runners gh attestation verify "${stem}.spdx.json" \ --bundle "${bundle}" \ --repo openai/tunnel-client \ --signer-workflow openai/tunnel-client/.github/workflows/release.yml \ --source-ref "refs/tags/${release}" \ --source-digest "${source_digest}" \ --signer-digest "${source_digest}" \ --predicate-type https://slsa.dev/provenance/v1 \ --deny-self-hosted-runners gh attestation verify "${stem}-licenses.txt" \ --bundle "${bundle}" \ --repo openai/tunnel-client \ --signer-workflow openai/tunnel-client/.github/workflows/release.yml \ --source-ref "refs/tags/${release}" \ --source-digest "${source_digest}" \ --signer-digest "${source_digest}" \ --predicate-type https://slsa.dev/provenance/v1 \ --deny-self-hosted-runners gh attestation verify SHA256SUMS.txt \ --bundle "${bundle}" \ --repo openai/tunnel-client \ --signer-workflow openai/tunnel-client/.github/workflows/release.yml \ --source-ref "refs/tags/${release}" \ --source-digest "${source_digest}" \ --signer-digest "${source_digest}" \ --predicate-type https://slsa.dev/provenance/v1 \ --deny-self-hosted-runners

./scripts/verify_release_archive.sh \ --flavor "${flavor}" \ --archive "${stem}.zip" \ --sbom "${stem}.spdx.json" \ --checksums SHA256SUMS.txt

To verify the complete release evidence set, download every asset into a clean directory and run both fail-closed contract verifiers:

mkdir release-evidence
gh release download "${release}" \
  --repo openai/tunnel-client \
  --dir release-evidence

./scripts/verify_release_provenance.sh \ --bundle "release-evidence/${bundle}" \ --artifact-dir release-evidence \ --release "${release}" \ --source-digest "${source_digest}" ./scripts/verify_release_evidence.sh \ --artifact-dir release-evidence \ --release "${release}" \ --source-digest "${source_digest}"

Use prefix=tunnel-client with flavor=client, or prefix=tunnel-client-runtime-cloudflared with flavor=runtime-cloudflared. The bundle is the release workflow's signed Sigstore evidence and supports verification without GitHub attestation lookup. It is emitted after SHA256SUMS.txt is attested, so it is intentionally not listed in that checksum file; gh attestation verify checks the bundle's signature, transparency-log material, signer workflow, source ref, source digest, and subject digest. The archive verifier then fails closed when the published checksums do not match, the ZIP is too large or has unsafe, duplicate, or non-regular members, its embedded sidecar differs from the downloaded sidecar, or the SPDX SHA256 inventory does not match the extracted payload. After it passes, import the matching .spdx.json file into the dependency scanner of your choice. Runtime releases also publish a matching *-scan-manifest.json that binds scanner scope, source archives, license evidence, and the release sidecars.

For a fully disconnected verification environment, capture a trusted root from an independently trusted online environment before disconnecting:

gh attestation trusted-root > trusted_root.jsonl

Transfer that file with the release evidence and add --custom-trusted-root trusted_root.jsonl to each gh attestation verify command above. The release bundle removes the GitHub attestation API dependency; the trusted-root file removes the remaining online trust-root lookup.

Build the CLI binary from a source checkout. The Make target stamps the checkout Git SHA into the version sent in User-Agent and X-Tunnel-Client-Version:

make admin-ui
make tunnel-client
./bin/tunnel-client help quickstart

If you invoke Go directly, stamp the same metadata explicitly:

module_path="$(go list -m -f '{{.Path}}')"
git_sha="$(git rev-parse HEAD)"
mkdir -p bin

go build \ -ldflags "-X ${module_path}/pkg/version.GitSHA=${git_sha}" \ -o bin/tunnel-client \ ./cmd/client

Narrow runtime artifacts

tunnel-client-runtime and tunnel-client-runtime-cloudflared are the runtime-only customer surfaces. They intentionally expose only run plus flag-based --help and --version; use the full tunnel-client binary for onboarding, admin, Codex, and profile-management commands. The Cloudflare flavor adds only the approved cloudflared.* settings and supervises a pinned cloudflared companion.

Build either binary from a source checkout with its Make target (the shorter aliases are equivalent):

make tunnel-client-runtime              # alias: make runtime
make tunnel-client-runtime-cloudflared  # alias: make runtime-cloudflared

The targets write platform-specific binaries under bin/_/ and stable paths at bin/tunnel-client-runtime and bin/tunnel-client-runtime-cloudflared (.exe on Windows). Inspect the exact runtime flags with ./bin/tunnel-client-runtime run --help or ./bin/tunnel-client-runtime-cloudflared run --help.

Run the narrow runtime against an HTTP MCP server:

export CONTROL_PLANE_API_KEY='...'
export CONTROL_PLANE_TUNNEL_ID='tunnel_0123456789abcdef0123456789abcdef'
export MCP_SERVER_URL='https://mcp.example.com/mcp'
./bin/tunnel-client-runtime run

For managed Cloudflare provisioning, use the Cloudflare flavor. Release archives place the pinned cloudflared executable beside the runtime; for a source-only build, point to an existing companion explicitly:

./bin/tunnel-client-runtime-cloudflared run \
  --cloudflared.managed \
  --cloudflared.path /path/to/cloudflared

To build the corresponding Linux images, use make build-image-runtime and make build-image-runtime-cloudflared; the Cloudflare image includes its pinned companion and both images use run as their entrypoint.

Contributors can run the compatibility suite with make test-runtime. Its host-binary checks compare the full client and runtime flavors with the same profile bytes, environment, flags, local control-plane/MCP/OAuth/proxy/TLS fixtures, and shutdown signal. The same target also packages native release-shaped ZIPs and checks that they verify, extract, identify the expected flavor, and expose run --help; that ZIP smoke is not a second fake-service parity run.

After building the runtime images, make runtime-container-compatibility runs a deployment smoke for their default and overridden entrypoints with read-only profile/Secret mounts and hardened container settings. It uses intentionally unreachable local endpoints to check startup surfaces and SIGTERM, not to compare image behavior with the full client or assert /readyz readiness. An optional local Kubernetes deployment smoke is available with `TUNNEL_CLIENT_RUNTIME_K8S_COMPAT=1 make runtime-k8s-compatibility; it requires Docker plus kind or k3d`, checks the runtime Pods' mounted profile/Secret and /healthz surface, and does not contact external services.

Public releases use plain semantic-version tags such as v0.0.10. Source archives from release tags carry the release version in pkg/version/VERSION. A plain go build from a downloaded release .tar.gz therefore reports the tag semantic version through tunnel-client --version, User-Agent, and the explicit control-plane version headers. Source-checkout builds made with the Make target or explicit linker flag above append the Git SHA to that semantic version.

Supported release archives also bundle pinned cloudflared 2026.8.2 beside the CLI for Linux amd64/arm64, macOS amd64/arm64, and Windows amd64/arm64. Official release images are published at ghcr.io/openai/tunnel-client for Linux amd64 and arm64; they bundle the matching companion. Pin an exact vX.Y.Z tag or digest for production. Stable releases also update the X.Y and latest aliases; prereleases do n

GitHub Stars & Activity

438Stars
87Forks
0Open issues
GoLanguage

GitHub Popularity

GitHub stars438
Forks87
Open issues0
Primary languageGo
License-
Stars gained today10
Created-
Last pushed-

Trending History

Daily boardrank #55 · ▲ 10 stars
Weekly boardrank #84 · ▲ 57 stars

Related AI Projects

1

coder / coder

Go★ 15,813⑂ 1,533▲ 402 stars
2

weave-os / router

Go★ 4,534⑂ 125▲ 56 stars
3

vshulcz / deja-vu

Go★ 878⑂ 86▲ 20 stars
4

itsmostafa / typesafe-mcp

Go★ 126⑂ 15
5

affaan-m / ECC

JavaScript★ 263,267⑂ 39,395▲ 1,012 stars
6

NousResearch / hermes-agent

Python★ 247,332⑂ 51,995
7

tensorflow / tensorflow

C++★ 200,205⑂ 76,961▲ 28 stars
8

Significant-Gravitas / AutoGPT

Python★ 187,453⑂ 46,009▲ 30 stars

More AI Rankings