System Overview
Gig'MCP is a security-first MCP gateway. It aggregates community MCP servers behind one streamable-HTTP endpoint per profile, runs each server in a kernel-enforced bubblewrap sandbox, and keeps credentials outside the sandbox — the real API key is injected by an embedded egress proxy only on HTTPS calls to allowlisted domains.
The gateway is a single static Go binary. The dashboard is a separate Next.js container. Everything deploys with Docker Compose. The runtime is Linux-only (bubblewrap, network namespaces, and seccomp don't exist on macOS); on a Mac it runs inside Docker.
Components
All of these live in the gigmcp monorepo. Apart from the dashboard, they all run inside the one gateway binary.
| Component | Source | What it does |
|---|---|---|
| MCP aggregator / router | internal/gateway | Serves streamable-HTTP MCP endpoints (/mcp/p/<profile> per profile, plus a transitional legacy /mcp and / mount), routes tool calls to the right sandboxed backend |
| Supervisor / spawner | internal/gateway/spawn.go, internal/sandbox, internal/netmgr | Builds the bwrap command, spawns one sandbox per (server × tenant), provisions a dedicated veth//30 subnet, and binds the sandbox IP to a tenant identity |
| In-sandbox bootstrap | cmd/bootstrap | Trusted init inside each sandbox: configures the veth and default route, drops all capabilities and uid, installs the seccomp filter, then execs the untrusted server |
| Egress MITM proxy | internal/proxy | Terminates HTTPS at a runtime CA, enforces the domain allowlist, swaps placeholder tokens for real credentials, writes the audit log |
| Vault | internal/vault | Envelope encryption (XChaCha20-Poly1305) for credentials at rest — see Vault & Encryption |
| OCI puller | internal/oci | Pulls digest-pinned images daemonlessly (no Docker socket) and extracts the server's entrypoint binary |
| Registry client / installer | internal/registry | Fetches the signed index.json, verifies its ed25519 signature before parsing, resolves manifests, and installs servers |
| Auth | internal/auth | Sole auth authority: a generic OIDC client that issues httpOnly sessions (local email+password login is designed but not yet implemented) |
| REST API | internal/api | Control plane for the dashboard (servers, profiles, credentials, users, audit) — see REST API |
| Store | internal/store | Repository interface with SQLite (zero-config default); Postgres is a design-level second driver with a "no Postgres-only features in core" rule |
| Web dashboard | web/ | Next.js app in its own container — pure frontend against the REST API, see Dashboard |
Architecture
┌──────────────────────── docker compose ────────────────────────┐
│ │
Claude Code / Cursor ───┼─▶ :8080 /mcp/p/<profile> ┌────── gateway (Go, 1 binary) ────┐ │
(bearer token, │ │ MCP aggregator / router │ │
streamable HTTP) │ │ Auth authority (OIDC, sessions) │ │
│ │ REST API ◀───────────┐ │ │
Browser ────────────────┼─▶ web (Next.js) ──────────┼────────────────────────┘ │ │
│ :3000 │ Vault (envelope enc, SQLite) │ │
│ │ Supervisor ──spawns──▶ sandboxes│ │
│ │ Egress MITM proxy ◀─only route─ │ │
│ └──────────┬──────────┬────────────┘ │
│ │ veth/IP │ │
│ ┌───────────▼──┐ ┌────▼──────────┐ │
│ │ slack-mcp │ │ github-mcp │… │
│ │ bwrap: userns│ │ placeholder │ │
│ │ netns seccomp│ │ token only │ │
│ └──────────────┘ └───────────────┘ │
└─────────────────────────────────────────────────────────────────┘
gigmcp-registry repo ──CI──▶ lint manifests ▶ build OCI images ▶ sign index.json ──▶ gateway pulls by digest
Life of a tool call (Tier 1 "sealed")
- Client → gateway. Your MCP client (Claude Code, Cursor, …) calls a tool on
http://<host>:8080/mcp/p/<profile>with the profile's bearer token. - Route / spawn. The gateway resolves the tool to a backend server for that profile. If the sandbox instance is cold, the supervisor spawns it from the binary that was extracted from the digest-pinned OCI image at install time: build the bwrap command, allocate a
/30subnet, create a veth pair, move the peer into the sandbox's network namespace, and registersandbox IP → (server, tenant)with the proxy. - Bootstrap hands off. Inside the sandbox, the trusted
cmd/bootstrapinit configures the link and a default route via the proxy, drops every capability, drops to uid 65534, installs the seccomp filter, andexecs the untrusted server binary. The server's environment contains a high-entropy placeholder token — never the real key. - Server makes an HTTPS call. The only route out of the sandbox is the proxy.
HTTPS_PROXYis set as a convenience, but route isolation is the actual enforcement. - Proxy identifies, checks, injects. The proxy resolves the connection's source IP to a tenant identity (unforgeable — the netns can only source addresses from its own
/30), checks the target domain against the manifest allowlist, and — if the request carries the placeholder in the configured header — replaces it with the vault-decrypted real key. - Forward + audit. The request goes to the real upstream; every ALLOW/DENY decision is logged with source IP, server, tenant, host, and method.
- Response streams back through the proxy, the sandbox, and the gateway to your MCP client.
See Egress Proxy & Credential Injection for steps 4–6 in detail and Sandbox Isolation for steps 2–3.
Monorepo layout
gigmcp/
├── cmd/
│ ├── gateway/ # main binary: mux, config, vault init, FORWARD-drop
│ ├── bootstrap/ # trusted in-sandbox init (net setup, cap drop, seccomp, exec)
│ ├── echo-mcp/ # bundled demo MCP server
│ └── fetch-mcp/ # bundled demo MCP server
├── internal/
│ ├── api/ # REST control plane
│ ├── auth/ # OIDC client + sessions
│ ├── config/ # env-driven configuration (GIG_* variables)
│ ├── gateway/ # MCP routing, profile host, sandbox spawning
│ ├── netmgr/ # host-side veth/subnet provisioning (netlink, CAP_NET_ADMIN only)
│ ├── oci/ # daemonless digest-pinned image pull + entrypoint extraction
│ ├── proxy/ # MITM egress proxy, runtime CA, IP→identity registry
│ ├── registry/ # signed-index client + installer
│ ├── sandbox/ # bwrap command construction
│ ├── seccomp/ # scoped seccomp-BPF filter
│ ├── store/ # SQLite-backed repository
│ └── vault/ # envelope encryption
├── web/ # Next.js dashboard (separate container)
├── docker-compose.yml
└── DESIGN.md # full decision record and threat model
The companion repo, gigmcp-registry, holds the manifest catalog, schema validators, and CI that publishes the signed index — see Registry Overview.
Ports and processes
| Listener | Default | Purpose |
|---|---|---|
| Gateway HTTP | :8080 (GIG_LISTEN) | MCP endpoints (/mcp/p/<profile>), REST API (/api/) |
| Egress proxy | 8081 (GIG_PROXY_PORT) | CONNECT-only MITM proxy; reachable from sandboxes via their /30 host-side IP |
| Web dashboard | :3000 | Next.js container |
The gateway container needs cap_add: NET_ADMIN (veth creation and namespace moves) — no SYS_ADMIN, no privileged mode. It currently also requires seccomp=unconfined, apparmor=unconfined, and systempaths=unconfined at the Docker layer; see the Security Model for why and what compensates for it. Full deployment steps are in Deployment and configuration reference in Configuration.