Documentation · v0.2

Deploying mcpdef

mcpdef is one static binary. Run it on a VM with Docker/Podman, or on Kubernetes with the bundled Helm chart. In both cases you provide one mcpdef.toml declaring the MCP servers to front (see CONFIG.md), and the gateway serves clients over Streamable HTTP on :7878 while the optional read-only admin server (Prometheus /metrics + a status UI) runs on :7879.

Security defaults. The admin server has no auth of its own, so it is never reachable off-host by default: compose binds it to loopback (127.0.0.1) only, and the Helm chart leaves it off (admin.enabled=false) — turn it on with admin.enabled=true and [gateway.admin] enabled = true in config, and keep it in-cluster / behind a NetworkPolicy or auth proxy. The binary has no in-process TLS — put a TLS-terminating reverse proxy (and [gateway.auth]) in front of :7878 for internet exposure. The audit ledger persists only if you give it a durable volume.

On a cloud VM (Docker or Podman) #

Files: compose.yaml and mcpdef.example.toml, reproduced in full under Reference files.

mkdir mcpdef && cd mcpdef               # save compose.yaml and mcpdef.example.toml here
cp mcpdef.example.toml mcpdef.toml      # declare your MCP servers
docker compose up -d                    # or: podman-compose up -d
docker compose logs -f mcpdef           # "mcpdef X.Y.Z ready · N upstream(s) · listening …"

Upgrade: bump the image: tag in compose.yaml, docker compose pull && docker compose up -d. State (the ledger) survives.

On Kubernetes (Helm) #

Chart: deploy/helm/mcpdef in the source tree (Chart.yaml, values.yaml, and the templates it renders).

# from the source tree
helm install mcpdef ./deploy/helm/mcpdef \
  --namespace mcpdef --create-namespace \
  --set-file config=./my-mcpdef.toml      # your config (declares the upstreams)

Or edit values.yaml's inline config and helm install mcpdef ./deploy/helm/mcpdef -n mcpdef --create-namespace.

Key values (values.yaml):

ValueDefaultPurpose
image.repository / image.tagmancube/mcpdef / chart appVersionthe image
configadmin-disabled stubthe full mcpdef.toml (declare your servers here)
admin.enabledfalsepublish the unauthenticated admin/metrics port (:7879); also set [gateway.admin] enabled = true in config
persistence.enabledfalsegive the audit ledger a PVC (else emptyDir, lost on restart); requires replicaCount: 1 (RWO, single-writer)
serviceMonitor.enabledfalsecreate a Prometheus-Operator ServiceMonitor for /metrics (requires admin.enabled=true)
service.typeClusterIPexpose via your own Ingress/LoadBalancer + TLS
resources, replicaCount1 replicanote: each replica keeps its own ledger; persistence.enabled pins this to 1

Reach it:

# MCP endpoint (in-cluster): http://mcpdef.mcpdef.svc:7878/mcp
# admin UI + /metrics (only when admin.enabled=true):
kubectl -n mcpdef port-forward svc/mcpdef 7879:7879
#   → http://localhost:7879

Metrics & Grafana #

Metrics live on the admin listener, so enable it first: --set admin.enabled=true (and [gateway.admin] enabled = true in config). Keep it private.

Notes #

Reference files #

The files this page refers to, exactly as they ship in the source tree.

compose.yaml #

# Run mcpdef on a single cloud VM with Docker or Podman.
#
#   1. cp mcpdef.example.toml mcpdef.toml   # then declare your MCP servers in it
#   2. docker compose up -d                 # or: podman-compose up -d
#   3. point MCP clients at  http://<vm>:7878/mcp
#      view the status UI at http://127.0.0.1:7879  (SSH-tunnel it; no auth)
#
# The admin port is bound to loopback (127.0.0.1) — it has no auth, so it is not
# exposed off-box. Put a TLS-terminating reverse proxy + [gateway.auth] in front
# of :7878 for real internet exposure (the binary has no in-process TLS).
services:
  mcpdef:
    image: mancube/mcpdef:0.2.1
    command: ["run", "--http", "--config", "/etc/mcpdef/mcpdef.toml"]
    ports:
      - "7878:7878"              # MCP Streamable HTTP (bring your own TLS proxy)
      - "127.0.0.1:7879:7879"    # admin /metrics + status UI — loopback only
    volumes:
      - ./mcpdef.toml:/etc/mcpdef/mcpdef.toml:ro
      - mcpdef-audit:/var/lib/mcpdef   # tamper-evident audit ledger (persisted)
    restart: unless-stopped

volumes:
  mcpdef-audit:

mcpdef.example.toml #

# Copy to mcpdef.toml and declare the MCP servers to front. Full reference:
# docs/CONFIG.md. This example enables the read-only admin server (metrics + UI).
[gateway]
listen = "0.0.0.0:7878"
audit  = "/var/lib/mcpdef/audit.log"   # persisted via the compose volume

[gateway.admin]
enabled = true
listen  = "0.0.0.0:7879"               # published to 127.0.0.1 only by compose

# --- declare your MCP servers (deny-by-default allowlist) ---
# A remote Streamable-HTTP MCP server:
# [[server]]
# id        = "internal"
# transport = "streamable-http"
# url       = "https://mcp.internal.example/mcp"
# tools     = ["list_issues", "get_file_contents"]
# deny      = ["delete_*"]

# A local stdio MCP server (its binary must exist in the mcpdef image — for
# arbitrary stdio servers, prefer wrapping them as their own HTTP service):
# [[server]]
# id        = "local"
# transport = "stdio"
# command   = ["mcp-server-foo", "--flag"]
# tools     = ["*"]