Skip to content

Platform Installation

Install MCP Runtime on a Kubernetes cluster in your cloud or on-premises environment. To try user and team workflows first, use the public platform walkthrough.

Choose your path

  • Try the public reference platform: follow the walkthrough; no cluster is required.
  • Self-host on an existing cluster: install the CLI, check cluster readiness, then follow the production-style setup below.
  • Contribute on local Kind: use the Development and Testing and Local Kind and Test Mode.

To adapt the project's complete infrastructure example, use Cluster Provisioning and Public Reference Deployment. The reference chooses K3s for Kubernetes and Docker/Caddy for a separate Keycloak VM. Select your distribution and identity provider before adopting its configuration.

Customer hostnames and shared-host routes

Public hostnames and paths are separate settings. Choose any DNS names with MCP_PLATFORM_INGRESS_HOST, MCP_REGISTRY_INGRESS_HOST, and MCP_MCP_INGRESS_HOST; they may all be the same hostname. The existing MCP_PLATFORM_DOMAIN convention remains a shortcut for the default subdomains. Every chosen hostname must resolve to ingress and be covered by the serving certificate.

For a single hostname, start from config/deployments/single-host.env.example. The public settings map to these setup flags:

Environment variable Setup flag Meaning
MCP_PLATFORM_PATH_PREFIX --platform-path-prefix Public dashboard path prefix (e.g. /platform); session and asset routes use this prefix
MCP_REGISTRY_PATH --registry-path Public registry browser entry path; Docker and Kubernetes clients still use /v2/
MCP_DOCS_PATH --docs-path Public path that redirects to the configured documentation URL
MCP_DOCS_URL --docs-url HTTPS documentation destination used by the docs route and dashboard links
MCP_GRAFANA_PATH_PREFIX --grafana-path-prefix Public admin-only Grafana path prefix
MCP_SETUP_MCP_AUTH_ISSUER_URL --mcp-auth-issuer-url Public HTTPS issuer URL for the bundled mcp-auth authorization server

The example gives the dashboard /platform/, its browser sessions /platform/auth/*, documentation /docs, the registry browser entry /registry, and admin-only Grafana /monitoring. Platform APIs retain /api/v1/*. /registry opens the platform's server catalog; it is not an OCI API prefix or a new image registry administration UI. Image clients use mcp.example.com/<team>/<image>:<tag> and contact https://mcp.example.com/v2/ automatically.

Custom platform or Grafana prefixes require a configured public platform hostname so setup can generate their ingress rules. Local test-mode defaults continue using the existing hostless routes.

When opting into bundled MCP Auth, an issuer of https://mcp.example.com/auth gives endpoints such as /auth/register and discovery at /.well-known/oauth-authorization-server/auth. Configure the identity provider's callback as https://mcp.example.com/auth/identity/callback. For an existing authorization server, retain it and configure your MCP servers' issuer accordingly; do not install a second server over an occupied /auth route. Individual MCP server prefixes remain configurable through server metadata.

Use the same env file for setup reruns. Explicit flags override its route settings. Paths must start with /, have no trailing slash (except the root dashboard /), and contain clean segments using letters, digits, _, -, ., and ~; traversal segments and escaped separators are rejected. Setup rejects overlapping managed paths and reserved /api, /v2, and /.well-known namespaces before writing to the cluster. When sharing the auth hostname, use a separate dashboard prefix to avoid its session /auth routes.

Existing enterprise certificates use --with-tls --provided-tls-secrets after the required TLS Secrets are provisioned. Certificates cover hostnames, so a single certificate can serve all these paths. Keep Secret namespace ownership, certificate renewal, and any external proxy's streaming/registry upload support in your deployment plan. An image-only update does not apply route changes; include the new configuration in the release's fresh-setup procedure.

Prerequisites

For a release CLI install, you need curl or wget on macOS/Linux, or PowerShell on Windows. Go and Make are only needed when building from source.

To install the platform, you also need:

  • Docker or a Docker-compatible client, with the daemon running and reachable. Setup image builds use BuildKit when DOCKER_BUILDKIT=1. Docker 29 does not always ship the buildx plugin. If build fails with "buildx component is missing or broken", install buildx or run setup with DOCKER_BUILDKIT=0.
  • kubectl on PATH, configured for the intended target cluster
  • A running Kubernetes cluster with DNS, storage, and ingress prepared. Start with Deployment Options, then Cluster Requirements.

Contributor and example workflows also use Go 1.26+, Make, curl, jq, Python 3, and Kind for local test-mode clusters. Run the repository's dependency checks from a source checkout:

make deps-install              # best-effort install for supported macOS/Linux hosts
STRICT_DEPS_CHECK=1 make deps-check

These checks cannot start Docker Desktop, create cloud credentials, or configure your kubeconfig. The Development and Testing owns source checkout and local cluster setup.

1. Install the CLI

Option A: install a release binary (no Go required). Select your operating system and copy the install command:

curl -fsSL https://raw.githubusercontent.com/mcp-runtime/mcp-runtime/main/install.sh | MCP_RUNTIME_OS=darwin sh
curl -fsSL https://raw.githubusercontent.com/mcp-runtime/mcp-runtime/main/install.sh | MCP_RUNTIME_OS=linux sh
irm https://raw.githubusercontent.com/mcp-runtime/mcp-runtime/main/install.ps1 | iex

The macOS/Linux installer detects the CPU and installs to ~/.local/bin without sudo. Windows installs to a user-local directory and adds it to your user PATH; open a new terminal after installation. Pin a specific release by setting MCP_RUNTIME_VERSION to its tag before running the installer. For example, run export MCP_RUNTIME_VERSION=vX.Y.Z on macOS/Linux or $env:MCP_RUNTIME_VERSION='vX.Y.Z' in PowerShell.

The macOS/Linux installer logs detection, download, and installation steps, shows download progress, and retries failed transfers. Color is automatic in a terminal; set NO_COLOR=1 to disable it.

Browse all binaries on the latest GitHub release. If macOS or Linux cannot find mcp-runtime, add ~/.local/bin to your shell's PATH, for example with export PATH="$HOME/.local/bin:$PATH".

Option B: build from source (requires Go 1.26+):

make deps
make build

This produces ./bin/mcp-runtime with version metadata from git describe --tags --match 'v*', the current commit, and UTC build time. For source builds, add the checkout’s bin directory to PATH with export PATH="$PWD/bin:$PATH" to run the commands below. Release binaries use the release tag exactly. Override with VERSION=<tag> make build when needed.

2. Confirm cluster readiness

mcp-runtime bootstrap

Before setup, confirm the target Kubernetes cluster is ready for registry pushes, image pulls, ingress, storage, and TLS. See Deployment Options to choose the right install shape for self-managed or managed Kubernetes, then cluster-readiness.md for distribution-specific preparation.

setup installs MCP Runtime resources into an already-running cluster. It does not configure node DNS, containerd or Docker registry trust, public DNS, TLS issuers, image pull credentials, or storage classes. Fix those prerequisites with your platform tooling before continuing.

bootstrap validates kubectl connectivity, CoreDNS, the default StorageClass, Traefik IngressClass, and MetalLB namespace. It only warns; fix gaps with your platform tooling, or bootstrap --apply --provider k3s to install bundled CoreDNS / local-path on k3s. After setup, run cluster diagnostics to validate the installed MCP Runtime resources, registry pulls, ingress, Platform, and operator readiness.

3. Choose production-style setup

Use this path for any cluster you intend to keep. That includes staging, internal shared clusters, externally reachable installs, or anything that needs stable registry, DNS, TLS, storage, and ingress ownership.

Before setup, make these decisions explicitly:

  • Registry: bundled registry with TLS, or a provisioned external registry
  • DNS: stable hostnames for registry, mcp, and platform
  • TLS: Let's Encrypt, enterprise ClusterIssuer, or preinstalled cert flow
  • Ingress: repo-managed Traefik or an existing platform ingress controller
  • Storage and retention: registry and platform persistence choices
  • Image pull auth: pull secrets, workload identity, or node-native registry auth

Read these first:

For production-oriented setup, choose the registry path explicitly. With a provisioned registry:

mcp-runtime bootstrap
mcp-runtime setup --registry-mode external --external-registry-url registry.example.com --with-tls --strict-prod

With the bundled registry serving internal HTTPS, setup generates an internal CA secret for the registry pod certificate unless you provide an existing ClusterIssuer. Configure every node to trust that CA for image pulls. Public ingress TLS can still use ACME:

mcp-runtime bootstrap
mcp-runtime setup --registry-mode bundled-https --with-tls --acme-email ops@example.com --strict-prod

If you want hostnames derived from one domain, set:

export MCP_PLATFORM_DOMAIN=example.com
export MCP_PLATFORM_ADMIN_EMAIL=admin@example.com
mcp-runtime setup --registry-mode external --external-registry-url registry.example.com --with-tls --strict-prod

That derives:

  • registry.example.com
  • mcp.example.com
  • platform.example.com

If you already have an external registry, provision it before setup so the cluster pulls from the same hardened image host you intend to keep:

mcp-runtime registry provision --url registry.example.com
mcp-runtime setup --registry-mode external --with-tls --strict-prod

For public/TLS setup, setup validates the host env even without --strict-prod. Use MCP_PLATFORM_DOMAIN or set MCP_PLATFORM_INGRESS_HOST, MCP_REGISTRY_INGRESS_HOST, and MCP_MCP_INGRESS_HOST explicitly. For bundled HTTPS with a public domain, set MCP_REGISTRY_ENDPOINT=registry.<domain> so kubelet pulls match the TLS certificate (not the registry ClusterIP). Set MCP_ADMIN_USERS for the OIDC platform-admin allowlist, or provide the password-admin pair below. --acme-email is only the certificate contact email.

A password dashboard login is separate from that OIDC promotion. Set both MCP_PLATFORM_ADMIN_EMAIL and MCP_PLATFORM_ADMIN_PASSWORD before setup. Setup rejects an incomplete pair before deploying the platform. A rerun preserves an existing complete pair when neither value is supplied; supplying both replaces it. With neither configured nor saved, setup warns that no password-admin account will be seeded and omits the empty Secret keys. Dev login remains off outside test mode. Keep passwords in a private env file.

For TLS termination on an external proxy with HTTP Traefik, put both UI_REQUIRE_HTTPS=false and UI_FORCE_SECURE_COOKIE=true in the file passed to setup --env-file. Setup forwards these settings to the UI and preserves them on reruns. The public origin must use HTTPS and the HTTP origin should only accept traffic from the proxy. This avoids redirect loops while retaining Secure session cookies; see reverse proxy setup.

When building setup images from a machine with a different CPU architecture than the cluster, set MCP_IMAGE_PLATFORM to the target node platform, for example MCP_IMAGE_PLATFORM=linux/amd64 for standard VPS/k3s nodes.

Tenant users open server-scoped Prometheus and Grafana views from Activity server rows. The platform API verifies access to the exact MCPServer and expands only allowlisted Prometheus queries; raw Prometheus remains internal. The bundled Prometheus discovers operator-managed MCPServer Services and scrapes metrics from their gateway sidecars.

You can also skip the saved provision step and pass --external-registry-url registry.example.com directly to setup.

If you use an internal CA instead of ACME, install the issuer first and point setup at it:

mcp-runtime setup --with-tls --tls-cluster-issuer <issuer-name> --strict-prod

What --strict-prod is for:

  • requires TLS
  • rejects dev-only registry assumptions such as registry.local
  • forces you onto a stable production-style registry endpoint

Do not use the contributor --test-mode flow as a production install guide. --test-mode is for local development and CI-like validation; it provisions a local cert-manager workload CA for mTLS tests and still builds and pushes local images and assumes the contributor registry and ingress shape.

Enterprise-provided TLS certificate files

Use this mode when enterprise IT supplies a certificate chain (fullchain.pem) and its matching private key (privkey.pem) but does not operate a cert-manager ClusterIssuer. Unlike --tls-cluster-issuer, this mode only references the Secrets below. Runtime never creates or renews a cert-manager Certificate.

Set INSTALL_KUBECONFIG to the kubeconfig for your installation cluster. The target namespaces must already exist when importing their Secrets.

Verify that the certificate SANs cover every public Runtime hostname, and keep the PEM files outside the repository and shell history. Kubernetes Secrets are namespace-scoped, so import the pair once for each Runtime ingress namespace:

kubectl --kubeconfig "$INSTALL_KUBECONFIG" -n registry create secret tls registry-tls \
  --cert=/secure/fullchain.pem --key=/secure/privkey.pem \
  --dry-run=client -o yaml | kubectl --kubeconfig "$INSTALL_KUBECONFIG" apply -f -

kubectl --kubeconfig "$INSTALL_KUBECONFIG" -n mcp-platform create secret tls mcp-platform-tls \
  --cert=/secure/fullchain.pem --key=/secure/privkey.pem \
  --dry-run=client -o yaml | kubectl --kubeconfig "$INSTALL_KUBECONFIG" apply -f -

Then run setup with static Secret mode:

mcp-runtime setup --kubeconfig "$INSTALL_KUBECONFIG" --with-tls --provided-tls-secrets --strict-prod

Do not combine --provided-tls-secrets with --acme-email or --tls-cluster-issuer. If you also deploy bundled mcp-auth, add its operator-managed TLS Secret to the mcp-platform namespace and pass its name through the optional --mcp-auth-tls-secret override.

Renewal

You renew certificates in this mode. Before the enterprise certificate expires, IT supplies a replacement matching pair; rerun the two kubectl create secret tls ... --dry-run=client -o yaml | kubectl --kubeconfig "$INSTALL_KUBECONFIG" apply -f - commands above. Traefik observes Secret updates and serves the replacement certificate. Verify the public endpoint's hostname and expiry after each rotation.

4. Configure and install the platform stack

Run the setup command selected in step 3 with any additional configuration below. The examples are alternatives; do not rerun bare setup after your configured install.

setup installs the platform pieces companies need for MCP operations: CRDs, mcp-runtime and catalog namespaces, the internal Docker registry, ingress wiring, the operator, and the bundled platform stack for gateway policy, analytics, audit, and observability.

--platform-mode chooses who can browse and publish servers on your installation. In public mode, visitors can browse its public catalog without signing in. This setting applies to your installation; it does not connect it to platform.mcpruntime.org.

Mode Default namespace behavior Behavior
org (default) mcp-servers-org Signed-in users publish and browse the org-wide catalog and their authorized team namespaces.
public mcp-servers-public Anonymous users browse public previews or public MCP listings; signed-in users also publish to org and team scopes.

For browser Google sign-in, provide the OAuth client ID before setup. Non-test public TLS installs (--platform-mode public --with-tls) fail fast unless GOOGLE_CLIENT_ID / MCP_GOOGLE_CLIENT_ID is set, or OIDC_ISSUER, OIDC_AUDIENCE, and OIDC_JWKS_URL are all set for another provider. For Google, setup uses the client ID as the OIDC audience and fills the standard Google issuer and JWKS URL when those values are not set explicitly:

export GOOGLE_CLIENT_ID=<client>.apps.googleusercontent.com
export MCP_PLATFORM_ADMIN_EMAIL=admin@example.com
mcp-runtime setup --with-tls --platform-mode public

For a non-Google OIDC provider, set OIDC_ISSUER, OIDC_AUDIENCE, and OIDC_JWKS_URL before setup. Reruns preserve existing values in mcp-platform/mcp-shared-config and mcp-observability/mcp-shared-config.

For multi-team or tenant-separated deployments, keep setup as the platform install and provision one namespace per team with mcp-runtime team create <slug> (platform API). Use the platform API to default team IDs, or set spec.teamID and subject.teamID directly in YAML; an explicit foreign subject.teamID delegates access to another team while the gateway still matches every non-empty subject field. See Multi-team isolation.

Common variants:

mcp-runtime setup --with-tls            # cert-manager TLS for the registry
mcp-runtime setup --platform-mode public # public preview catalog namespace
mcp-runtime setup --without-platform-stack    # skip the request-path stack
mcp-runtime setup --test-mode           # local Kind/dev build+push path
mcp-runtime setup --test-mode --test-multi-replica # opt in to the normal replica layout
mcp-runtime setup --storage-mode hostpath # single-node cluster with no dynamic provisioner
mcp-runtime setup --parallel-builds     # build and publish setup images in parallel

Defaults if you pass nothing: --ingress traefik, --ingress-manifest config/ingress/overlays/http, --registry-mode auto, --registry-type docker, --registry-storage 20Gi, --platform-mode org, and --storage-mode dynamic. --parallel-builds changes image build and publish only; cluster, registry, TLS, and rollout sequencing stay the same.

--registry-storage sets the PVC request before its first creation, so a fresh test install can use a smaller positive size such as 5Gi. Setup checks an existing claim before applying registry resources and rejects a size below its requested or allocated capacity. Kubernetes cannot shrink a PVC. On a rerun, retain that size or increase it if the storage class supports expansion.

To enable optional adapter client certificates on gateway server routes, add --mtls-cluster-issuer <cluster-issuer> alongside --with-tls. Name an ready enterprise cert-manager issuer with a signing root separate from registry TLS. In test mode, setup provisions the bundled mcp-runtime-ca by default; production rejects that shared workload root, including renamed copies of its signing key. See CA lifecycle and migration. Production certificate issuance also requires an approval policy; see the linked adapter guide. Set MCP_ADAPTER_CERTIFICATES=true to turn the feature on; production must also set MCP_TRUST_DOMAIN (for example mcpruntime.org). OAuth remains optional: omit spec.auth for cert-only routes, or add spec.auth when direct clients need a bearer. See Agent Adapters.

The bundled mcp-auth authorization server is optional and off by default. Check the provider with mcp-runtime auth provider-check --issuer-url <issuer>, then enable it with --with-mcp-auth-server; outside test mode it also needs --mcp-auth-signing-key-secret. The issuer defaults to https://auth.<MCP_PLATFORM_DOMAIN>/mcp-auth, and the operator reconciles resource audiences from OAuth MCPServers. With managed TLS, setup provisions the auth issuer certificate automatically; --mcp-auth-tls-secret is only an optional override for externally managed certificates. See MCP authorization.

Every setup flag and its default is listed in the CLI reference.

Local development notes

For Kind or other local setups where traffic reaches Traefik through kubectl port-forward or a NodePort but the ingress controller does not publish Ingress.status.loadBalancer.ingress[], run setup with permissive ingress readiness:

export MCP_INGRESS_READINESS_MODE=permissive
mcp-runtime setup --test-mode --ingress-manifest config/ingress/overlays/http
kubectl port-forward -n traefik svc/traefik 18080:8000 18443:8443

Then use http://127.0.0.1:18080/<publicPathPrefix>/mcp for plain Ingress MCP traffic. When MCP_ADAPTER_CERTIFICATES=true, gateway routes use Traefik IngressRoute on websecure only — probe them at https://127.0.0.1:18443/<publicPathPrefix>/mcp (local Kind typically needs -k / insecure TLS skip for Traefik's default certificate).

MCPServer stuck in PartiallyReady while traffic works

Strict readiness (the default) waits for the Ingress to publish status.loadBalancer.ingress[]. Many dev and NodePort-style controllers route traffic without ever publishing it. permissive treats an Ingress with rules as ready. Keep strict for production clusters that rely on published load-balancer status.

5. Confirm health

After mcp-runtime auth login --api-url <platform-url>, use status for a quick authenticated platform API check. Use the other status commands for cluster, registry, and workload health.

mcp-runtime status
mcp-runtime cluster status
mcp-runtime registry status
mcp-runtime ops status

6. Deploy your first server

The server deploy flow (init → validate → build → push → deploy → grant → adapter) is covered step-by-step in the learning modules:

Quick reference:

mcp-runtime auth login --api-url <platform-url>
mcp-runtime server init my-server --from-server http://localhost:8088
mcp-runtime server validate --metadata-dir .mcp
mcp-runtime server build image my-server --tag v1
mcp-runtime server push --image ... --scope tenant
mcp-runtime server deploy my-server --scope tenant --metadata-dir .mcp

mcp-runtime auth login --api-url and MCP_PLATFORM_API_URL must be the HTTPS origin when the front door redirects http to https. server push POSTs the image archive. The upload client rejects redirects that change the method, scheme, or origin and reports the final HTTPS origin to configure with auth login --api-url; it does not resend the upload as a GET. GET calls such as team list can still succeed against an HTTP base.

The hop in front of /api/v1/runtime/registry/push must allow a large, long-lived request body. A server image archive can be hundreds of mebibytes. The bundled registry path already expects that for /v2/. An outer proxy that leaves /api/ at the default 1 MiB limit rejects the push before the API sees it. Configure a suitable body limit (for example Nginx client_max_body_size 0 on this upload route) and request/read timeouts long enough for the largest supported image. Apply the same policy to the registry /v2/ routes; this does not change API authorization.

Org mode supports --scope org and --scope tenant; --scope public requires public mode. Public mode supports all three scopes, with separate org and public namespaces. tenant is a publish scope, not a platform mode. Tenant publish also needs a team membership. An admin API key can create a team and still be unable to push, because that key has no team. Create the team, add a user as a member, and push with that user's token. For example:

mcp-runtime team create first-team
# Add an existing account using its user ID:
mcp-runtime team user add first-team <user-id> --role owner
# Or create a new account and membership together:
mcp-runtime team user create first-team --email <email> --role owner

The create command prompts for a hidden password twice. Use --password-stdin to provide it through stdin in non-interactive workflows.

Then sign in as that team member before publishing or deploying. The Teams UI also supports Add existing user. Membership changes preserve an existing account's password.

7. Observe live traffic and policy

Use the platform dashboard and API first:

mcp-runtime auth login --api-url <platform-url>
mcp-runtime status
# Dashboard: http://localhost:18080/ (Kind) or https://platform.<domain>/

Admin/operator kubectl diagnostics (ops * requires admin cluster access):

mcp-runtime ops port-forward ui          # Governance + dashboard
mcp-runtime ops port-forward grafana     # Metrics + traces + logs
mcp-runtime ops logs gateway --follow    # Tail the proxy

Local alternative: contributor test-mode cluster (Kind)

For local development, CI, or contributing to the repo, use the Kind-based test-mode path. The contributor docs own this path completely:

Quick path (requires kind on PATH; the linked guide creates an isolated test kubeconfig so this does not use the ambient or production context):

make deps && make build
export PATH="$PWD/bin:$PATH"
# Follow Local Kind and Test Mode to create/export the test kubeconfig first.
export KUBECONFIG="$HOME/.kube/test-mcp-runtime-config"
mcp-runtime bootstrap
mcp-runtime cluster doctor
mcp-runtime setup --test-mode --ingress-manifest config/ingress/overlays/http
kubectl port-forward -n traefik svc/traefik 18080:8000 18443:8443
mcp-runtime cluster diagnostics

See Local Kind and Test Mode for fresh-device and existing-cluster instructions, including the isolated kubeconfig setup.

bootstrap and cluster doctor run before setup: bootstrap reports (and on k3s can install) missing cluster prerequisites, cluster doctor checks nodes, storage, ingress, DNS, and TLS readiness. cluster diagnostics runs after setup to validate what was installed.

Local surfaces: platform http://localhost:18080/, plain Ingress MCP http://localhost:18080/<server-name>/mcp, adapter-certificate MCP https://localhost:18443/<server-name>/mcp (use -k for the local default cert when MCP_ADAPTER_CERTIFICATES=true).

End-to-end flow

flowchart LR
    A[Install CLI] --> B[bootstrap + setup]
    B --> C[auth login]
    C --> D[server init<br/>build, push, deploy]
    D --> E[grant init + apply]
    E --> F[adapter or admin session]
    F --> G[Traffic through gateway]
    G --> H[Observe in UI + Grafana]

Next steps

For background on grants, sessions, trust, and side effects, see Concepts.