Skip to main content

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.

ComponentSourceWhat it does
MCP aggregator / routerinternal/gatewayServes 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 / spawnerinternal/gateway/spawn.go, internal/sandbox, internal/netmgrBuilds 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 bootstrapcmd/bootstrapTrusted 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 proxyinternal/proxyTerminates HTTPS at a runtime CA, enforces the domain allowlist, swaps placeholder tokens for real credentials, writes the audit log
Vaultinternal/vaultEnvelope encryption (XChaCha20-Poly1305) for credentials at rest — see Vault & Encryption
OCI pullerinternal/ociPulls digest-pinned images daemonlessly (no Docker socket) and extracts the server's entrypoint binary
Registry client / installerinternal/registryFetches the signed index.json, verifies its ed25519 signature before parsing, resolves manifests, and installs servers
Authinternal/authSole auth authority: a generic OIDC client that issues httpOnly sessions (local email+password login is designed but not yet implemented)
REST APIinternal/apiControl plane for the dashboard (servers, profiles, credentials, users, audit) — see REST API
Storeinternal/storeRepository interface with SQLite (zero-config default); Postgres is a design-level second driver with a "no Postgres-only features in core" rule
Web dashboardweb/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")

  1. Client → gateway. Your MCP client (Claude Code, Cursor, …) calls a tool on http://<host>:8080/mcp/p/<profile> with the profile's bearer token.
  2. 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 /30 subnet, create a veth pair, move the peer into the sandbox's network namespace, and register sandbox IP → (server, tenant) with the proxy.
  3. Bootstrap hands off. Inside the sandbox, the trusted cmd/bootstrap init configures the link and a default route via the proxy, drops every capability, drops to uid 65534, installs the seccomp filter, and execs the untrusted server binary. The server's environment contains a high-entropy placeholder token — never the real key.
  4. Server makes an HTTPS call. The only route out of the sandbox is the proxy. HTTPS_PROXY is set as a convenience, but route isolation is the actual enforcement.
  5. 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.
  6. Forward + audit. The request goes to the real upstream; every ALLOW/DENY decision is logged with source IP, server, tenant, host, and method.
  7. 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

ListenerDefaultPurpose
Gateway HTTP:8080 (GIG_LISTEN)MCP endpoints (/mcp/p/<profile>), REST API (/api/)
Egress proxy8081 (GIG_PROXY_PORT)CONNECT-only MITM proxy; reachable from sandboxes via their /30 host-side IP
Web dashboard:3000Next.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.