This document is for contributors working on tunnel-client.
./scripts/build_admin_ui.sh ./adminui ./pkg/adminui/assets
# or
make admin-ui
go build ./...
go build -o bin/tunnel-client ./cmd/clientUse ./bin/tunnel-client for local source-checkout runs unless bin/ is on
your PATH.
Before creating a release tag, stamp the source version so downloaded release archives build with the tag semantic version:
make release-source-version VERSION=1.2.3
make release-tag VERSION=1.2.3The release workflow validates the multi-architecture image on master and
publishes it to ghcr.io/openai/tunnel-client for Linux amd64 and arm64
from release tags. Stable v1.2.3 tags publish v1.2.3, 1.2.3, 1.2,
latest, and sha-<full-commit-sha>; prereleases publish only their exact
version tags and commit SHA. Semver build metadata (+...) is rejected
because Docker tags cannot represent it. The image build uses Buildx cache,
emits an SBOM attestation, and records signed GitHub artifact provenance.
Homebrew Formula tooling is available for deterministic rendering and explicit
test publishes, but production tap publication is not wired into the release
workflow yet. Add that integration only after the tap bootstrap and scoped
GitHub App credential path are provisioned. Do not document
brew install openai/tools/tunnel-client as supported before a compatible
stable Formula is present in the tap.
Exercise the deterministic renderer and tap-PR publisher with:
bash ./scripts/generate_homebrew_formula_test.sh
bash ./scripts/publish_homebrew_formula_test.shThe standalone Homebrew formula smoke workflow downloads checksums from an
already-published release, renders the Formula, and runs brew readall,
brew install, and brew test without uploading artifacts, creating a
release, or minting a tap write token. For an explicit remote draft publish,
run publish_homebrew_formula.sh --draft with human gh auth and a
test-tunnel-client-* branch. --allow-prerelease exists only for these
test paths; normal stable publication continues to reject prereleases.
go test ./...The wire-contract tests treat openapi.json as executable
documentation:
go test ./pkg/controlplane/wiretypes ./pkg/controlplane/internalThey validate the documented endpoint methods, OpenAPI examples, command discriminators, and serialized response payloads against the published schema.
The e2e/ tests use in-repo test doubles under testsupport/:
testsupport/mocktunnelservice: simulates the control plane poll/response endpoints.testsupport/mockmcpserver: a Streamable HTTP MCP server double.
Run:
go test ./e2e -count=1There are two supported wrapper patterns for tests that start an MCP server and need tunnel-client in the path:
- Remote control plane: start your MCP server, then start
tunnel-client runwithCONTROL_PLANE_API_KEY,--control-plane.tunnel-id, and--mcp-server-urlor--mcp-command. Use this when a test should exercise a hosted control plane. - Local control plane: start your MCP server, then start
tunnel-client dev proxy --mcp-server-url <url> --print-json. This runs a local control plane plus tunnel-client in one process and prints connection JSON that tests can use for JSON-RPC requests.
dev proxy runs the local control plane and tunnel-client in one process. It
prefers a Unix-domain socket for tunnel-client control-plane traffic when the OS
supports it and falls back to TCP otherwise. It starts no health/admin listener
by default; pass --health-listen-addr 127.0.0.1:0 or
--health-url-file <path> only when a test needs /healthz, /readyz,
/metrics, or /ui. The --backend auto|go|rust flag defaults to auto, and
--engine-queue-backend inmem|redis defaults to inmem. Public builds use the
Go backend unless an optional Rust backend adapter is linked into the binary;
explicit --backend rust fails clearly when unavailable. Redis selects the
linked Rust backend for auto, rejects go, and requires
--engine-redis-url <url> or TUNNEL_ENGINE_REDIS_URL.
External MCP ingress is TCP by default: --listen defaults to 127.0.0.1:0,
mcp_transport is tcp, and mcp_url remains populated. Pass
--listen-unix-socket <path> instead for Unix ingress; it is mutually
exclusive with --listen, and the JSON contains mcp_transport: "unix",
mcp_unix_socket, and mcp_url_path. This external socket is separate from
the temporary Unix socket used by tunnel-client for internal control-plane
traffic.
Stable touch points:
- Go tests can import
github.com/openai/tunnel-client/pkg/localproxyand calllocalproxy.Start. - Python tests can copy or import
wrappers/mcp-tunnel-client-proxy/python/mcp_tunnel_client_proxy.py. - TypeScript tests can copy or import
wrappers/mcp-tunnel-client-proxy/typescript/mcp_tunnel_client_proxy.ts. - Copyable example subprojects live under
examples/.
cmd/client: CLI entrypointpkg/*: implementation packagese2e/: end-to-end tests using in-repo mockstestsupport/: test helpers and doubles