Sandbox
@openziti/mcp-gateway

Zero-trust gateway for MCP tools over OpenZiti

MCP Gateway aggregates multiple MCP backends behind one secure endpoint. It uses OpenZiti, zrok, and Agora to move tool access over encrypted tunnels instead of public ports.

48 stars8 forksGoUpdated 22d ago
Who it's for

Builders who want their agents to reach private tools, remote MCP servers, or team-shared tool sets securely.

What it delivers

You can give agents access to internal tools without opening public endpoints or managing VPN access.

What it does

Aggregate multiple MCP backends

Combines stdio, HTTP(S), zrok, and Agora backends into one MCP endpoint.

Zero-trust access

Uses cryptographic identity, mTLS, and OpenZiti so tools are not exposed on public IPs.

Per-client isolation

Creates separate sessions for each client so backend state does not leak across users.

Tool filtering

Lets you allow or deny specific tools per backend with exact names or wildcards.

Bridge and share support

Can expose one MCP server with `mcp-bridge` or connect through shared zrok tokens.

Agora discovery and tunnels

Can serve and discover MCP backends through Agora catalogs and Layer 1 tunnels.

How to get it

  1. 1Run
    go install github.com/openziti/mcp-gateway/cmd/...@latest
  2. 2Note: mcp-gateway requires zrok v2.0.x or later. Currently the best release is zrok…
    zrok2 enable <your-zrok-token>  # get token at https://api-v2.zrok.io
  3. 3Run
    mcp-gateway run config.yml
    # outputs: {"share_token":"abc123..."}

README

MCP Gateway

Zero-trust access to MCP tools over OpenZiti

MCP Gateway lets AI assistants securely access internal tools without exposing public endpoints. Built on OpenZiti, zrok, and Agora, it provides cryptographically secure, zero trust connectivity with no attack surface.

MCP Gateway is sponsored by NetFoundry as part of its portfolio of solutions for secure workloads and agentic computing. NetFoundry is the creator of OpenZiti and zrok.

The Trifecta

Three simple components that work together:

ComponentPurpose
mcp-toolsConnects MCP clients to remote zrok shares or Agora tunnels (stdio or HTTP)
mcp-gatewayAggregates multiple backends into one secure endpoint over zrok and/or Agora
mcp-bridgeExposes a single MCP server to the network over zrok or Agora
flowchart LR
    A[Agent] -->|stdio| B[mcp-tools]
    B -->|zrok or Agora| C[mcp-gateway]
    C -->|stdio / zrok / Agora / HTTP| D[MCP Servers]
    C -->|https| E[Remote MCP APIs]

Why?

Problem: MCP servers typically run locally via stdio. To access tools on remote machines or share them across a team, you need to expose endpoints—creating security risks. Securing exposed MCP tooling can be complicated.

Solution: MCP Gateway uses OpenZiti's overlay network to create "dark services" that:

  • Never listen on public IPs
  • Require cryptographic identity to access
  • Work through NATs and firewalls without port forwarding
  • Can publish and serve through Agora catalogs and Layer 1 tunnels
  • Are incredibly simple to deploy securely

Quick Start

New to MCP Gateway? See the Getting Started Guide for a complete walkthrough.

1. Install

go install github.com/openziti/mcp-gateway/cmd/...@latest

2. Enable zrok

Note: mcp-gateway requires zrok v2.0.x or later. Currently the best release is zrok v2.0.0-rc7

zrok2 enable <your-zrok-token>  # get token at https://api-v2.zrok.io

3. Run a Gateway

Create config.yml:

aggregator:
  name: "my-gateway"
  version: "1.0.0"

backends:
  - id: filesystem
    transport:
      type: stdio
      command: npx
      args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/documents"]

  - id: github
    transport:
      type: stdio
      command: npx
      args: ["-y", "@modelcontextprotocol/server-github"]
      env:
        GITHUB_TOKEN: "ghp_xxx"
mcp-gateway run config.yml
# outputs: {"share_token":"abc123..."}

4. Connect from Agent

Add to agent config:

{
  "mcpServers": {
    "my-tools": {
      "command": "mcp-tools",
      "args": ["run", "abc123..."]
    }
  }
}

That's it. Your agent can now use tools from both backends through a single secure connection.

Use Cases

Aggregate Multiple Tool Servers

Combine filesystem, GitHub, database, and custom tools into one connection:

backends:
  - id: fs
    transport: { type: stdio, command: mcp-server-filesystem, args: ["/data"] }
  - id: github
    transport: { type: stdio, command: mcp-server-github }
  - id: postgres
    transport: { type: stdio, command: mcp-server-postgres }

Tools are namespaced automatically: fs:read_file, github:create_issue, postgres:query.

Expose a Remote Tool Server

Run mcp-bridge on a remote machine to expose a local MCP server:

# on remote server
mcp-bridge mcp-server-custom --config /etc/custom.yml
# outputs share token

# from anywhere
mcp-tools run <share_token>

Chain Bridges and Gateways

Gateway can connect to remote bridges (or other gateways) as backends:

backends:
  - id: remote-tools
    transport:
      type: zrok
      share_token: "token-from-bridge"

Serve and Discover Through Agora

Gateway, bridge, and tools can use Agora Layer 1 tunnels in addition to zrok. A gateway can serve over zrok and Agora at the same time, publish a catalog advertisement, and connect to backends exposed by mcp-bridge --network=agora.

agora:
  enabled: true
  serve:
    enabled: true
  advertisement:
    publish: true

backends:
  - id: remote-filesystem
    transport:
      type: agora
      agora_tunnel: filesystem-relay
mcp-tools run --agora mcp-gateway-engineering

See Agora Integration for configuration, CLI flags, integration files, and smoke scenarios.

Connect to HTTP and HTTPS MCP Servers

Gateway can aggregate remote MCP servers over HTTP(S), using either Streamable HTTP (the default) or the legacy SSE transport. Set protocol: sse explicitly for an SSE endpoint. type: https is strict and only accepts https:// endpoints. type: http supports both http:// and https://, but plaintext HTTP requires explicit opt-in.

backends:
  - id: remote-api
    transport:
      type: https
      endpoint: "https://mcp.example.com/mcp"
      headers:
        Authorization: "Bearer sk-abc123"

  - id: legacy-api
    transport:
      type: https
      endpoint: "https://mcp.internal.corp/sse"
      protocol: "sse"
      tls:
        ca_cert_file: "/etc/ssl/certs/internal-ca.pem"

This works alongside stdio and zrok backends — mix and match as needed.

For local development or trusted internal networks, you can opt into plaintext HTTP explicitly:

backends:
  - id: local-dev
    transport:
      type: http
      endpoint: "http://localhost:8080/mcp"
      allow_insecure: true

HTTP backend clients connect directly and refuse redirects by default. Set allow_environment_proxy: true or allow_redirects: true on the transport only when that wider network behavior is deliberate.

Persistent Shares

By default, mcp-gateway and mcp-bridge create an ephemeral share that disappears when the process exits. Persistent shares are stored server-side in zrok, so a gateway or bridge can stop and restart without changing the share token.

Ephemeral shares are closed and owner-only by default. A bridge can grant other zrok accounts with a repeatable flag:

mcp-bridge --access-grant teammate@example.com mcp-filesystem /data

For mcp-gateway, put the same account emails under zrok.share.access_grants in the gateway configuration. The creating account does not belong in the list; it already has access.

# create a persistent share with a chosen name
zrok2 create share -s my-gateway
# the chosen name is the share token

# use the token in a gateway config (share_token: my-gateway) or bridge
mcp-gateway run config.yml
mcp-bridge --share-token my-gateway npx -y @modelcontextprotocol/server-filesystem /home/user

# the gateway/bridge can restart and reconnect to the same share

# when done, delete the share
zrok2 delete share my-gateway

If you omit the name, zrok generates a random token:

zrok2 create share
# outputs the share token

The token name must be 3–32 characters, lowercase alphanumeric and hyphens ([a-z0-9-]).

HTTP Transport

All components support HTTP-based MCP transport in addition to stdio.

Serve via HTTP with mcp-tools:

# expose a zrok share as a local HTTP server
mcp-tools http <share_token> --bind 127.0.0.1:8080

# expose an Agora tunnel as a local HTTP server
mcp-tools http --agora <tunnel> --bind 127.0.0.1:8080

Options:

  • --stateless - Stateless mode (no session persistence)
  • --json-response - Prefer JSON responses over streamed responses
  • --session-idle-timeout <duration> - Close inactive local sessions after this long; default 30m, 0 disables

The gateway and bridge natively serve MCP over Streamable HTTP through zrok and Agora. Use mcp-tools http when you need a local Streamable HTTP endpoint for clients that don't support the stdio transport provided by mcp-tools directly.

mcp-tools http serves any number of local agents, and each one gets its own MCP session on the remote gateway or bridge. The zrok access or Agora attachment is opened once and held for the life of the process; a fresh fabric session is opened when a local agent connects and closed when it disconnects, its session expires, its initialization fails, or mcp-tools shuts down. Backend state stays isolated per agent, exactly as it is for agents connecting to the gateway directly. In --stateless mode there is no session to own, so a fabric session is opened and closed per request — correct, but a full MCP handshake across the overlay on every call.

On shutdown mcp-tools waits up to five seconds for handshakes still in flight, so sessions completing during shutdown are released rather than stranded. The fabric HTTP clients set no timeout, so that wait is bounded rather than open-ended, and the bound carries an accepted cost: a remote session is created when its initialize request arrives at the gateway or bridge, so a handshake whose response is still in flight when the wait expires leaves a real remote session — holding real backend resources — that mcp-tools can no longer terminate. It logs how many it abandoned, and the far side reclaims them through its own idle expiry. The alternative, waiting indefinitely, would let a wedged overlay hold shutdown open forever.

Inactive Streamable HTTP sessions expire after 30 minutes so a client that disappears without terminating its session cannot retain dedicated backend connections or bridge subprocesses indefinitely. Set session_idle_timeout in gateway YAML, or pass --session-idle-timeout <duration> to mcp-bridge or mcp-tools http, to tune that bound. An explicit 0 disables idle expiry and may retain those resources until the client terminates its session or the server shuts down.

Tool Filtering

Control which tools are exposed per backend:

backends:
  - id: filesystem
    transport: { type: stdio, command: mcp-server-filesystem }
    tools:
      mode: allow
      list:
        - "read_file"
        - "list_directory"
        # write operations not exposed

  - id: github
    tools:
      mode: deny
      list:
        - "delete_*"
        # everything except delete operations

Architecture

MCP Gateway creates isolated sessions for each connecting client:

flowchart LR
    subgraph Clients
        A[Client A]
        B[Client B]
    end

    A --> G[Gateway]
    B --> G

    subgraph Session A
        G --> A1[Backend 1]
        G --> A2[Backend 2]
    end

    subgraph Session B
        G --> B1[Backend 1]
        G --> B2[Backend 2]
    end

Each client gets dedicated backend connections—no shared state, no cross-talk.

Building from Source

git clone https://github.com/openziti/mcp-gateway.git
cd mcp-gateway
go build ./cmd/mcp-gateway
go build ./cmd/mcp-bridge
go build ./cmd/mcp-tools

Each binary has a version subcommand that prints build metadata:

mcp-gateway version

Local builds report a developer build (e.g. v0.1.x [developer build]); release binaries are stamped with the version, commit, build date, branch, and builder.

Documentation

License

Apache 2.0 - see LICENSE

Files in the repo

Repository payload32 top-level entries
  • .github
  • aggregator
  • agora
  • bin
  • bridge
  • cmd
  • docker
  • docs
  • e2e
  • etc
  • gateway
  • ipc
  • model
  • streamable
  • tools
  • .gitattributes
  • .gitignore
  • .goreleaser-darwin.yml
  • .goreleaser-linux-amd64.yml
  • .goreleaser-linux-arm64.yml
  • .goreleaser-release.yml
  • .goreleaser-windows.yml
  • CHANGELOG.md
  • CODE_OF_CONDUCT.md
  • CONTRIBUTING.md
  • CONTRIBUTORS
  • go.mod
  • go.sum
  • LICENSE
  • Makefile
  • README.md
  • SECURITY.md

Discussion (0)

Ask about usage, or say what you built with it

Sign in to join the discussion.

No comments yet. Be the first to say what this is good for.

More connectors

High-performance code intelligence MCP server. Indexes codebases into a persistent knowledge graph — average repo in milliseconds. 158 languages, sub-ms queries, 99% fewer tokens. Single static binary, zero dependencies.

43k

Universal provider proxy for OpenAI Codex & Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code

14k
okf-memory/
okf-agent-memory

Git-native persistent memory for AI coding agents. Implements Google OKF v0.2 with sub-300µs in-memory BM25 search, embedded MCP server, and progressive disclosure. Slashes token bloat by 80% with zero external databases or dependencies. Built in pure Go.

547
tirth8205/
code-review-graph

Local-first code intelligence graph for MCP and CLI. Builds a persistent map of your codebase so AI coding tools read only what matters, with benchmarked context reductions on reviews and large-repo workflows.

31k
2akouwu/
reverify

Stop your AI from making things up — it proposes, deterministic tools decide, every claim checked against ground truth with evidence. Grounded facts and context survive resets. Reverse engineering is the proving ground. MCP server + CLI.

1.1k
t8y2/dbxConnectors

20 MB lightweight cross-platform database client for 90+ databases, including MySQL, PostgreSQL, SQLite, Redis, MongoDB, DuckDB, SQL Server, and Dameng. Built-in AI, MCP Server, CLI, desktop and Docker. | 轻量级跨平台数据库管理工具,支持 MySQL、PostgreSQL、SQLite、Redis、MongoDB、达梦等 90+ 数据库,提供桌面端、Docker、CLI、内置 AI 助手和 MCP Server。

19k