One gateway, several URLs
A gateway can answer on several URLs at once: its direct URL, plus any tunnel or ingress URL in front of it. List them all in its config:- Tokens. Each URL is a token audience. The gateway accepts a token that names any of its URLs, and refuses a token that names none of them. Policy is the same on every URL: it keys on the agent, not on the URL it came in on.
- Metadata. The gateway’s protected resource metadata names the URL a client came in on, so each client asks for a token for the URL it uses.
- Host names. The gateway answers only requests for its own host names,
which protects against DNS rebinding. A request for any other name gets a
403. A gateway listening on loopback also answers to
localhost,127.0.0.1and[::1]. If a proxy in front of it rewrites the Host header to another name, add that name tolisten.allowed_hosts. - Registration. Only registered URLs are audiences, and only a project
manager registers one. The URLs in the config when a gateway enrolls are
registered then, under the enrollment token. A linked gateway reports its
public_urlson every sync; one you add to the config later waits on the project’s Gateways page until a project manager registers it there, since whoever can edit the config or holds the gateway’s identity shouldn’t be able to add audiences on their own. You can also register a URL that isn’t in the config. A URL you remove from the config is dropped within ten minutes, or at once on the Gateways page. A URL another live gateway holds can’t be registered: the Gateways page and the gateway’s log say so (public URL not registered). - Revoking. Revoking a gateway frees its URLs for another gateway. Stop the revoked gateway’s process too: it keeps its last bundle, and so keeps accepting tokens that name those URLs.
- TLS. The gateway requires TLS whenever it listens off loopback. Run a
tunnel next to it over loopback (in one Kubernetes pod, or one network
namespace in Compose), or give the gateway a certificate (
tls).
public_urls when it is the only way in that rewrites Host.
Which option fits
In every option, the hosted agent’s vendor sees query results, because it is
the client. Midplane never does.
Claude: Anthropic MCP tunnels
Anthropic’s MCP tunnels are in research preview. For claude.ai they need the Enterprise plan and a request to Anthropic; tunnels for the Claude Console and API are separate. Set one up with Anthropic’s guide first.- Parts. You run
cloudflaredand Anthropic’smcp-proxy.cloudflaredholds an outbound connection to Cloudflare. Traffic inside it is encrypted again from Anthropic tomcp-proxywith a certificate from your own CA, so Cloudflare carries ciphertext. - URL. A route named
midplanegetshttps://midplane.<tunnel domain>. Register that URL; the connector URL in claude.ai ishttps://midplane.<tunnel domain>/mcp. - Loopback. By default
mcp-proxyconnects only to RFC 1918 addresses. To reach the gateway over loopback, setupstream.allowed_ipsto127.0.0.1/32. That replaces the default list, so the proxy reaches nothing off this network namespace, though anything else listening on its loopback (such ascloudflared’s metrics) is in reach too. Routes decide which host names go where. - Host header. Anthropic doesn’t document which Host
mcp-proxysends upstream. If it sends127.0.0.1:7433, a loopback gateway answers to it, and its metadata names its first URL: list the tunnel URL first. - Names. Anthropic’s proxy runs as
mcp-proxy, reads/etc/mcp-gateway/config.yaml, and its Helm values call itgateway. Name Midplane’s servicemidplane-gateway, nevermcp-proxyormcp-gateway.
Docker Compose
The gateway sharesmcp-proxy’s network namespace, as cloudflared does, so
the proxy reaches it on 127.0.0.1. Take the image digests and the
certificate setup from Anthropic’s Compose guide.
Kubernetes
Anthropic’s Helm chart (mcp-tunnel) runs cloudflared and mcp-proxy in a
pod of its own and takes no extra containers, so the gateway can’t join it on
loopback. The hop from the proxy to the gateway then crosses the cluster
network, where the gateway requires TLS:
- Give the gateway a certificate from your internal CA (
tlsin its config), listen on0.0.0.0, and put a Service namedmidplane-gatewayin front of it. Add a NetworkPolicy that lets only the tunnel’s pods reach port 7433. The proxy may send the Service’s name as Host, so add it tolisten.allowed_hosts(not topublic_urls: it isn’t a URL agents use). - Route the chart to that Service over
https, and have the proxy trust your CA withupstream.tls.ca_file(see Anthropic’s Helm guide for mounting it):
ChatGPT and Codex: OpenAI Secure MCP Tunnel
OpenAI’s Secure MCP Tunnel serves ChatGPT developer mode, Codex and the Responses API, not publicly listed apps. You run OpenAI’s open-sourcetunnel-client, which long-polls OpenAI over outbound
HTTPS and forwards each request to the gateway. TLS ends at OpenAI, which is
the client anyway.
- Setup. Create a tunnel as OpenAI’s guide describes,
then point
tunnel-clientat the gateway:MCP_SERVER_URL=http://127.0.0.1:7433/mcp. Your authorization server is Midplane Cloud, on another origin than the gateway, so add its origin toMCP_OAUTH_TRUSTED_ORIGINS. Its docs ask for--harpoon.allow-plaintext-httpto discover OAuth metadata over plain loopback HTTP. - Host header.
tunnel-clientsends the upstream’s own address as Host,127.0.0.1:7433here. A gateway listening on loopback answers to it. - Resource URL: not verified yet.
tunnel-client’s docs say it rewrites theresourcein the gateway’s metadata to an OpenAI URL for your tunnel, so ChatGPT would ask Midplane Cloud for a token for that URL, which must be one of the gateway’s URLs. OpenAI doesn’t document its form, and Midplane registers only URLs whose path ends in/mcp. Until a real run settles this, ChatGPT through OpenAI’s tunnel may not finish signing in.
Docker Compose
tunnel-client shares the gateway’s network namespace:
Kubernetes
The gateway andtunnel-client run in one pod and talk over loopback, the
pattern OpenAI documents. Midplane has no Helm chart yet; this is the pod to
deploy.
midplane enroll and keep the identity it prints in the secret.
Your own endpoint
When a hosted agent has no vendor tunnel, give the gateway a public URL:- Your load balancer or ingress, in front of the gateway. Allow only the vendor’s egress ranges: Anthropic publishes its IP addresses, OpenAI its ChatGPT connector ranges. TLS ends at your load balancer, or passes through to the gateway’s own certificate.
- A public deployment in your own cloud account, with
tlsand the gateway listening on0.0.0.0.
public_urls. If the load balancer rewrites Host, add that
name to listen.allowed_hosts.