MCP Forge
View Markdown
MCP Forge installation.mdDownload .md

Install MCP Forge

Install the latest stable MCP Forge binary or run the published container. No account is required.

MCP Forge is the governed publishing layer for MCP tools. Version v1 connects PostgreSQL, MySQL and SQL Server; its connector model keeps the product identity independent from any one source type.

API connectors are part of the product direction, but they are not included in v1. Current installation and configuration examples therefore use database connectors only.

Documentation is versioned by major release (v1). The latest download and image tags follow the newest stable v1 release. For repeatable production deployments, record the version reported by runku version and pin that exact release during the next maintenance window.

For the guided first-run experience, use the MCP Forge quickstart. This page documents the manual installation path and the operational choices the quick installer makes on your behalf.

Installation contract

A complete binary deployment has five independent parts:

Component Purpose Must persist
runku binary Serves the portal and MCP endpoints Replace only during upgrades
config.yaml Listener, store location, connectors and OIDC verification Yes; back up with deployment configuration
Service environment Admin-store and connector DSNs Yes; keep in a secrets manager or restricted file
Admin store Policies, identities, roles and API-key hashes Yes; PostgreSQL is recommended for production
Audit store Local portal/report history and shipping outbox Yes when local history matters

The target databases configured under connectors[] are not installation state and are never interchangeable with Runku's private admin store.

Binary installation

Linux x86-64

curl --fail --location --output runku.tar.gz \
  https://downloads.runku.dev/latest/runku_linux_amd64.tar.gz
curl --fail --location --output checksums.txt \
  https://downloads.runku.dev/latest/checksums.txt
grep " runku_linux_amd64.tar.gz$" checksums.txt | sha256sum --check
tar -xzf runku.tar.gz
sudo install -m 0755 runku /usr/local/bin/runku
runku version

macOS Apple silicon

curl --fail --location --output runku.tar.gz \
  https://downloads.runku.dev/latest/runku_darwin_arm64.tar.gz
curl --fail --location --output checksums.txt \
  https://downloads.runku.dev/latest/checksums.txt
grep " runku_darwin_arm64.tar.gz$" checksums.txt | shasum -a 256 --check
tar -xzf runku.tar.gz
sudo install -m 0755 runku /usr/local/bin/runku
runku version

Windows x86-64

Run PowerShell as Administrator:

$BaseUrl = "https://downloads.runku.dev/latest"
Invoke-WebRequest "$BaseUrl/runku_windows_amd64.zip" -OutFile runku.zip
Invoke-WebRequest "$BaseUrl/checksums.txt" -OutFile checksums.txt
$Expected = ((Select-String " runku_windows_amd64.zip$" checksums.txt).Line -split " ")[0]
$Actual = (Get-FileHash runku.zip -Algorithm SHA256).Hash.ToLower()
if ($Actual -ne $Expected.ToLower()) { throw "Checksum verification failed" }
New-Item -ItemType Directory -Force "C:\Program Files\Runku" | Out-Null
Expand-Archive runku.zip -DestinationPath "C:\Program Files\Runku" -Force
& "C:\Program Files\Runku\runku.exe" version

Add C:\Program Files\Runku to the system PATH, then open a new terminal.

Direct downloads

Platform Architecture Latest stable Immutable v1.0.0
Linux x86-64 Download Download
Linux ARM64 Download Download
macOS Intel Download Download
macOS Apple silicon Download Download
Windows x86-64 Download Download

SHA-256 checksums are published with every channel update. Production automation should use the immutable release URLs.

Choose the filesystem layout

The examples below use predictable system locations:

Platform Binary Bootstrap configuration Persistent data Restricted environment
Linux/macOS /usr/local/bin/runku /etc/runku/config.yaml /var/lib/runku /etc/runku/runku.env
Windows C:\Program Files\Runku\runku.exe C:\ProgramData\Runku\config.yaml C:\ProgramData\Runku Service manager or protected wrapper

On Linux, create the unprivileged service account and directories before writing configuration:

# The service account cannot log in interactively.
sudo useradd --system --home /var/lib/runku --shell /usr/sbin/nologin runku
# Configuration and state are separate so permissions and backups are clear.
sudo install -d -m 0750 /etc/runku
sudo install -d -o runku -g runku -m 0750 /var/lib/runku

On macOS, create the same directories and use the non-root account that will own the launchd process:

# launchd will run Runku as the current account, not as root.
sudo install -d -m 0750 /etc/runku /var/lib/runku
sudo chown "$(id -un)" /var/lib/runku

On Windows:

# ProgramData persists configuration and runtime state outside Program Files.
New-Item -ItemType Directory -Force "C:\ProgramData\Runku" | Out-Null

Create the bootstrap configuration

This production-oriented example uses a dedicated PostgreSQL admin store, one PostgreSQL source connector, local audit history and API-key authentication. The same connector section accepts mysql or sqlserver.

# HTTP listener shared by the local portal and all MCP connector routes.
server:
  # Place a TLS reverse proxy in front before exposing this port externally.
  port: 8080

# Runku's private operational database. It contains policy and identities,
# never rows from the source configured under connectors[].
store:
  # PostgreSQL is recommended for production and multiple Runku instances.
  driver: postgres
  # Keep the dedicated admin-store credential outside this file.
  dsn: ${RUNKU_MCP_STORE_DSN}

# External sources published through independent governed MCP endpoints.
connectors:
  # Stable identifier used by the URL, policy and operational commands.
  - id: primary
    # MCP clients for this source connect to /mcp/primary.
    path: /mcp/primary
    database:
      # Supported values in v1: postgres, mysql or sqlserver.
      driver: postgres
      # This credential belongs to the source, not the Runku admin store.
      dsn: ${RUNKU_CONNECTOR_DSN}

# Optional local reporting buffer, separate from both databases above.
audit:
  # Persist this path and include it in backups when local history matters.
  sqlite_path: /var/lib/runku/runku-audit.db

# Require API keys and/or verified OIDC tokens on protected routes.
auth:
  enabled: true

On Windows, change audit.sqlite_path to C:/ProgramData/Runku/runku-audit.db. YAML accepts forward slashes and avoids Windows escaping ambiguity.

For a small single-instance installation, replace the store block with:

# Embedded admin store; suitable for evaluation and small single-node setups.
store:
  # No external admin database server is required.
  driver: sqlite
  # Keep this file on persistent storage and include it in backups.
  path: /var/lib/runku/runku.db

Supply secrets outside YAML

Create /etc/runku/runku.env on Linux or macOS:

# Store and connector credentials have different users and responsibilities.
sudo sh -c 'umask 077; cat > /etc/runku/runku.env' <<'EOF'
RUNKU_MCP_STORE_DSN='postgres://runku:<password>@<admin-db>:5432/runku?sslmode=require'
RUNKU_CONNECTOR_DSN='postgres://reader:<password>@<source-db>:5432/app?sslmode=require'
EOF

Restrict it to the account that will run Runku. For a runku system account:

# The service needs read access; other local users must not see DSNs.
sudo chown root:runku /etc/runku/config.yaml /etc/runku/runku.env
sudo chmod 0640 /etc/runku/config.yaml
sudo chmod 0600 /etc/runku/runku.env

On macOS, make the configuration, environment and launcher readable only by the non-root account selected for launchd:

# Replace ownership without exposing DSNs to other local accounts.
sudo chown "$(id -un)" /etc/runku/config.yaml /etc/runku/runku.env
sudo chmod 0600 /etc/runku/config.yaml /etc/runku/runku.env

On Windows, load the values into the elevated shell used for initialization; the service manager must receive the same values later:

# These values exist only in the current PowerShell process.
$env:RUNKU_MCP_STORE_DSN = "postgres://runku:<password>@<admin-db>:5432/runku?sslmode=require"
$env:RUNKU_CONNECTOR_DSN = "postgres://reader:<password>@<source-db>:5432/app?sslmode=require"

Configure OIDC instead of bootstrap API keys

OIDC is optional. It verifies tokens from an identity provider you operate; it does not store roles in YAML. Add this under auth before initialization:

# Authentication verifies the token; authorization remains in the admin store.
auth:
  # Reject requests without a valid API key or OIDC bearer.
  enabled: true
  oidc:
    # Keycloak realm, Google issuer or Entra issuer ending in /v2.0.
    issuer: https://id.example.com/realms/company
    # Required token audience; configure the same value in the IdP.
    audience: runku-mcp
    # Public Authorization Code + PKCE client used by the local portal.
    portal_client_id: runku-portal
    # Google normally uses true; Entra and Keycloak normally use false.
    prefer_id_token: false

Use the provider-specific values from Google Workspace and Entra ID or Keycloak and generic OIDC.

Initialize the admin store

Load the service environment, then run the supported first-boot initializer:

# Export restricted environment values without copying them into config.yaml.
set -a
. /etc/runku/runku.env
set +a
# Tests every configured source and initializes the empty admin store.
sudo -u runku --preserve-env=RUNKU_MCP_STORE_DSN,RUNKU_CONNECTOR_DSN \
  runku auth init --config /etc/runku/config.yaml

On macOS, run initialization as the same account declared in the launchd definition:

# The current account owns the restricted config and service environment.
set -a
. /etc/runku/runku.env
set +a
runku auth init --config /etc/runku/config.yaml

On Windows:

# Uses the environment values loaded in the previous step.
& "C:\Program Files\Runku\runku.exe" auth init `
  --config "C:\ProgramData\Runku\config.yaml"

When OIDC is absent, initialization creates one unrestricted bootstrap API key, prints it once and writes .runku-init-token beside config.yaml. Copy the token into a secrets manager, then delete that marker. Runku intentionally refuses to start while the plaintext marker remains. When OIDC is present, the initializer skips bootstrap-key generation.

Initialization does not expose tables or enable mutations. Those decisions are stored per connector after installation.

Run once in the foreground

Before registering a service, start Runku in the foreground and verify the exact configuration that the service will use:

# Linux: keep this terminal open while performing the first health checks.
sudo -u runku --preserve-env=RUNKU_MCP_STORE_DSN,RUNKU_CONNECTOR_DSN \
  runku serve --config /etc/runku/config.yaml

# macOS: run as the account selected for launchd.
set -a; . /etc/runku/runku.env; set +a
runku serve --config /etc/runku/config.yaml
# Press Ctrl+C after the portal and connector checks succeed.
& "C:\Program Files\Runku\runku.exe" serve `
  --config "C:\ProgramData\Runku\config.yaml"

Confirm that the portal loads at /portal/, the expected connector route is present, authentication rejects a request without credentials, and startup logs show successful source introspection. Stop the foreground process before registering the managed service.

Register a Linux systemd service

Confirm the unprivileged account created during layout preparation has only the required files:

sudo chown -R runku:runku /var/lib/runku
sudo chown root:runku /etc/runku/config.yaml /etc/runku/runku.env

Create /etc/systemd/system/runku.service:

[Unit]
# Start only after the host considers networking available.
Description=Runku MCP Forge
After=network-online.target
Wants=network-online.target

[Service]
# Run the published binary in the foreground; systemd owns its lifecycle.
Type=simple
User=runku
Group=runku
EnvironmentFile=/etc/runku/runku.env
ExecStart=/usr/local/bin/runku serve --config /etc/runku/config.yaml
# Recover from process failures without creating a tight restart loop.
Restart=on-failure
RestartSec=5
# Reduce access to host facilities not required by Runku.
NoNewPrivileges=true
PrivateTmp=true

[Install]
# Start automatically during normal multi-user boot.
WantedBy=multi-user.target

Enable and inspect it:

# Reload units after every service-file change.
sudo systemctl daemon-reload
sudo systemctl enable --now runku.service
sudo systemctl status runku --no-pager
sudo journalctl -u runku -n 100 --no-pager

Register a macOS launchd service

Create a small restricted launcher that loads /etc/runku/runku.env and then replaces itself with the Runku process. Save it as /etc/runku/service.sh:

#!/bin/sh
# Export values loaded from the restricted environment file.
set -a
. /etc/runku/runku.env
set +a
# launchd monitors this foreground process and restarts it when required.
exec /usr/local/bin/runku serve --config /etc/runku/config.yaml

Set the launcher and secret-file permissions to the macOS account that will run Runku, then create /Library/LaunchDaemons/dev.runku.mcp.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <!-- Stable launchd service identifier. -->
  <key>Label</key><string>dev.runku.mcp</string>
  <!-- Replace YOUR_ACCOUNT with the non-root account that owns runtime data. -->
  <key>UserName</key><string>YOUR_ACCOUNT</string>
  <key>ProgramArguments</key>
  <array><string>/etc/runku/service.sh</string></array>
  <!-- Start during boot and recover if the foreground process exits. -->
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <!-- Keep service output in the persistent data directory. -->
  <key>StandardOutPath</key><string>/var/lib/runku/runku.log</string>
  <key>StandardErrorPath</key><string>/var/lib/runku/runku-error.log</string>
</dict></plist>

Register and inspect it:

# Validate the plist before installing it.
plutil -lint dev.runku.mcp.plist
sudo install -m 0644 dev.runku.mcp.plist /Library/LaunchDaemons/dev.runku.mcp.plist
sudo launchctl bootstrap system /Library/LaunchDaemons/dev.runku.mcp.plist
sudo launchctl print system/dev.runku.mcp

Register a managed Windows startup task

The published binary runs in the foreground. On Windows, the supported script uses Task Scheduler as the process supervisor: it runs as SYSTEM, starts at boot and retries after failure. Store the DSNs in a PowerShell wrapper whose ACL allows only SYSTEM and local administrators, then register that wrapper with New-ScheduledTaskAction, New-ScheduledTaskTrigger -AtStartup and Register-ScheduledTask.

The complete, reviewed implementation is the versioned Windows installer script. For a manual Windows deployment, download it and inspect the section titled Register and start the managed background service; it creates no external account and installs no third-party service wrapper.

Check or restart the managed process with:

# Inspect the supervisor state and last result.
Get-ScheduledTask -TaskName "Runku MCP Forge"
Get-ScheduledTaskInfo -TaskName "Runku MCP Forge"
# Apply configuration changes with a controlled restart.
Stop-ScheduledTask -TaskName "Runku MCP Forge"
Start-ScheduledTask -TaskName "Runku MCP Forge"

Docker Compose installation

Create compose.yaml:

services:
  # Dedicated PostgreSQL database for Runku's internal admin store.
  # Do not point this service at a customer/source database.
  postgres:
    # Pin an exact major/minor image in controlled production environments.
    image: postgres:17-alpine
    # Restart after host or process failures unless explicitly stopped.
    restart: unless-stopped
    environment:
      # Database, login and secret used only by the Runku admin store.
      POSTGRES_DB: runku
      POSTGRES_USER: runku
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
    volumes:
      # Persist policies, roles, identities and API-key hashes across restarts.
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      # Prevent Runku from starting before its admin database accepts traffic.
      test: ["CMD-SHELL", "pg_isready -U runku -d runku"]
      interval: 10s
      timeout: 5s
      retries: 10

  # Free self-hosted MCP Forge distribution.
  runku:
    # `latest` tracks the stable channel; pin a version for controlled upgrades.
    image: registry.runku.dev/runku:latest
    restart: unless-stopped
    depends_on:
      postgres:
        # Start only after the internal store is ready.
        condition: service_healthy
    ports:
      # Host port : container port for portal and MCP endpoints.
      - "8080:8080"
    # Source connector DSNs and other secrets can be supplied from this file.
    env_file: .env
    environment:
      # Internal-store DSN. This is separate from connector DSNs in config.yaml.
      RUNKU_MCP_STORE_DSN: postgres://runku:${POSTGRES_PASSWORD}@postgres:5432/runku?sslmode=disable
    volumes:
      # Persist bootstrap config and the temporary first-token safety marker.
      - ./runku-config:/etc/runku
      # Persistent local runtime data such as the optional audit buffer.
      - runku-data:/var/lib/runku
    # Start the published binary with the mounted bootstrap configuration.
    command: ["serve", "--config", "/etc/runku/config.yaml"]

volumes:
  # Named volumes survive container replacement and image upgrades.
  postgres-data:
  runku-data:

Create runku-config/config.yaml beside compose.yaml:

# Container listener exposed by compose.yaml on host port 8080.
server:
  port: 8080

# Private admin state stored in the dedicated postgres Compose service.
store:
  # PostgreSQL data persists in the postgres-data named volume.
  driver: postgres
  # Compose injects this complete DSN into the Runku container.
  dsn: ${RUNKU_MCP_STORE_DSN}

# External business source; it is not the postgres service above.
connectors:
  - id: primary
    path: /mcp/primary
    database:
      # Change to mysql or sqlserver when appropriate.
      driver: postgres
      # Source credential loaded from the Compose environment.
      dsn: ${RUNKU_CONNECTOR_DSN}

# Local report/audit buffer persisted in the runku-data volume.
audit:
  sqlite_path: /var/lib/runku/runku-audit.db

# First boot creates an API key unless an oidc block is added here.
auth:
  enabled: true

Create .env with restrictive permissions. This file is used for Compose substitution and is passed to the Runku container through env_file:

# Password for the dedicated PostgreSQL admin-store container.
POSTGRES_PASSWORD=replace-with-a-long-random-password
# DSN for the external source; use a least-privilege database account.
RUNKU_CONNECTOR_DSN=postgres://reader:[email protected]:5432/app?sslmode=require

Do not commit or publish .env. On Unix, use chmod 0600 .env.

docker compose pull
docker compose run --rm runku auth init --config /etc/runku/config.yaml
# API-key mode only: save the token, then remove its plaintext safety marker.
if [ -f runku-config/.runku-init-token ]; then
  cat runku-config/.runku-init-token
  rm runku-config/.runku-init-token
fi
docker compose up -d
docker compose exec runku runku version
docker compose logs --tail=100 runku

If initialization generated .runku-init-token, the service intentionally refuses to start until that token is copied to secure storage and the marker is removed from runku-config. Remove only the marker after confirming the token is safely stored; do not delete the PostgreSQL admin store or its volume.

For production, replace latest with the exact version printed by the last command, for example registry.runku.dev/runku:v1.0.0.

Production storage

MCP Forge supports SQLite and PostgreSQL admin stores. PostgreSQL is the recommended production mode and supports multiple service instances sharing the same roles, identities, API keys and connector policies. SQLite remains a simple option for evaluation and small single-node installations.

The admin store is separate from connected PostgreSQL, MySQL or SQL Server sources. Back up the PostgreSQL admin database, config.yaml and the audit store according to the same recovery point.

Post-installation verification

Do not treat a running process as a complete verification. Check each boundary:

  1. runku version reports the expected pinned release.
  2. Startup logs show successful admin-store connection and source introspection.
  3. /portal/ loads through the intended hostname or local URL.
  4. An unauthenticated request to a protected route is rejected.
  5. The initial API key or OIDC login reaches the portal.
  6. Each connector appears at its configured /mcp/<id> route.
  7. An exposed, masked table omits the protected columns from both tool schema and results.
  8. A table or operation not allowed by policy is blocked and audited.

Keep the service bound to a trusted network until authentication, TLS and the first policy have passed these checks. If a reverse proxy terminates TLS, make sure it forwards long-lived MCP connections without buffering or an aggressive request timeout.

Logs and common failures

Symptom Check
Startup rejects an unknown YAML field Compare the versioned configuration reference; parsing is intentionally strict
Admin store connection fails Verify RUNKU_MCP_STORE_DSN, DNS, TLS mode and that the database is dedicated to Runku
Connector connects but introspection fails Grant catalog visibility and read access required for the selected schemas
Service refuses to start after initialization Save the initial token and remove only .runku-init-token beside config.yaml
Portal opens but login fails Verify issuer discovery, token audience, portal client ID and redirect URI
Connector has no tools Apply an explicit policy; installation intentionally exposes nothing
Service works in a shell but not after boot Confirm the service account receives the same DSN environment and can read configuration/data paths

Log locations depend on the process supervisor:

Upgrade and rollback

  1. Record runku version, the configuration checksum and the currently pinned container/binary version.
  2. Back up the PostgreSQL or SQLite admin store, local audit store, config.yaml and service definition in the same maintenance window.
  3. Read the target version's release notes before changing the binary.
  4. Download and checksum the exact immutable release, or pull its exact image tag. Do not leave latest in a controlled production definition.
  5. Stop the service, replace only the binary/image, and keep the previous artifact available for rollback.
  6. Start the service and repeat the post-installation verification, including one allowed and one blocked tool call.
  7. If verification fails, stop the new process, restore the previous artifact and restore the matching admin-store backup only when the release notes say the store format changed.

For a systemd binary deployment, replacement is typically:

# Preserve the old binary as the immediate rollback artifact.
sudo systemctl stop runku
sudo cp /usr/local/bin/runku /usr/local/bin/runku.previous
sudo install -m 0755 ./runku /usr/local/bin/runku
sudo systemctl start runku
sudo systemctl status runku --no-pager

Remove the managed process

Unregistering the process does not automatically delete configuration, secrets, admin state or audit history. This protects against accidental data loss.

# Linux: stop automatic execution and remove only the unit definition.
sudo systemctl disable --now runku
sudo rm /etc/systemd/system/runku.service
sudo systemctl daemon-reload

# macOS: unregister and remove only the launchd definition.
sudo launchctl bootout system/dev.runku.mcp
sudo rm /Library/LaunchDaemons/dev.runku.mcp.plist
# Windows: remove only the managed startup task.
Unregister-ScheduledTask -TaskName "Runku MCP Forge" -Confirm:$false

After taking a final backup, remove the binary, /etc/runku or C:\ProgramData\Runku, /var/lib/runku, and the PostgreSQL database only if you explicitly intend to erase the installation. Those destructive removals are deliberately not bundled into a single command.

Continue with MCP Forge configuration.