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 versionmacOS 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 versionWindows 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" versionAdd 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/runkuOn 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/runkuOn Windows:
# ProgramData persists configuration and runtime state outside Program Files.
New-Item -ItemType Directory -Force "C:\ProgramData\Runku" | Out-NullCreate 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: trueOn 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.dbSupply 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'
EOFRestrict 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.envOn 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.envOn 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: falseUse 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.yamlOn 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.yamlOn 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.envCreate /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.targetEnable 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-pagerRegister 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.yamlSet 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.mcpRegister 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: trueCreate .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=requireDo 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 runkuIf 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:
runku versionreports the expected pinned release.- Startup logs show successful admin-store connection and source introspection.
/portal/loads through the intended hostname or local URL.- An unauthenticated request to a protected route is rejected.
- The initial API key or OIDC login reaches the portal.
- Each connector appears at its configured
/mcp/<id>route. - An exposed, masked table omits the protected columns from both tool schema and results.
- 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:
- Linux:
journalctl -u runku. - macOS example:
/var/lib/runku/runku.logandrunku-error.log. - Windows: Task Scheduler history plus the Runku process output destination configured by your wrapper or monitoring agent.
- Compose:
docker compose logs runkuanddocker compose logs postgres.
Upgrade and rollback
- Record
runku version, the configuration checksum and the currently pinned container/binary version. - Back up the PostgreSQL or SQLite admin store, local audit store,
config.yamland service definition in the same maintenance window. - Read the target version's release notes before changing the binary.
- Download and checksum the exact immutable release, or pull its exact image
tag. Do not leave
latestin a controlled production definition. - Stop the service, replace only the binary/image, and keep the previous artifact available for rollback.
- Start the service and repeat the post-installation verification, including one allowed and one blocked tool call.
- 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-pagerRemove 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:$falseAfter 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.