Get Helio running in under 5 minutes. By the end, you'll have an MCP governance proxy intercepting tool calls, enforcing policy rules, and recording an audit trail — with a live dashboard showing everything.
- Node.js 22+ — check with
node --version jq(optional) — used in examples below for pretty-printing JSON. If unavailable, remove| jqand read raw JSON output.- An MCP server to govern — any server that speaks Streamable HTTP, SSE, or stdio. Helio works with any spec-compliant MCP server (e.g. FastMCP, the official MCP SDKs) with zero changes to the server. If you don't have one, use the echo server included in the examples.
Scaffold a new configuration file:
npx @gethelio/proxy initThis creates a helio.yaml in your current directory with commented defaults. Alternatively, install globally:
npm install -g @gethelio/proxy
helio init@gethelio/proxy includes the dashboard assets in the same package. There is no separate dashboard npm install step.
Open helio.yaml and set upstream.url to point at your MCP server:
version: '1'
upstream:
url: 'http://localhost:8080/mcp'
transport: streamable-http
listen:
port: 3000
host: '127.0.0.1'
dashboard:
enabled: true
port: 3100
api_secret: '${HELIO_DASHBOARD_SECRET}'
policies:
default: allow
rules:
- name: block-destructive
match:
annotations:
destructiveHint: true
action: deny
feedback:
message: 'Destructive operations are blocked by policy.'
suggestion: 'Use a non-destructive alternative.'
- name: allow-reads
match:
annotations:
readOnlyHint: true
action: allow
audit:
storage: sqlite
path: ./helio-audit.db
retention: 90d
include_responses: trueThis configuration blocks any tool marked as destructive, explicitly allows read-only tools, and falls through to default: allow for everything else. Every tool call is recorded to a local SQLite database.
If you use the ${HELIO_DASHBOARD_SECRET} placeholder above, export it before starting:
export HELIO_DASHBOARD_SECRET="$(openssl rand -hex 32)"If you started from helio init, the generated helio.yaml already includes a secret, so you can keep that value instead.
See the Configuration Reference for all available fields and defaults.
npx @gethelio/proxy start
# or: helio start (if installed globally)You should see output like:
Helio proxy listening on http://127.0.0.1:3000
Policies: 2 rules loaded (default: allow)
Upstream: http://localhost:8080/mcp (streamable-http)
Audit: ./helio-audit.db (retention: 90d)
Dashboard API listening on http://127.0.0.1:3100
Approvals: timeout 300s, default on timeout: deny, 0 channels configured
Rate limits: enabled
Spend limits: enabled
Config: helio.yaml
Watching helio.yaml for policy changes
Note: Use
-cto specify a different config file:npx @gethelio/proxy start -c production.yamlOn startup, Helio also sends a synthetic upstream
tools/listto warm the tool-annotation cache before first traffic. If the prime attempt cannot complete quickly, Helio continues startup and retries in the background. During that window, annotation matching remains fail-closed using MCP defaults.If
dashboard.enabled: true, startup requires bundled dashboard assets to be present. If assets are missing, Helio exits with an actionable error instead of silently serving API-only mode.
Instead of connecting your MCP client directly to the upstream server, point it at the proxy on http://localhost:3000/mcp.
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"my-server": {
"url": "http://localhost:3000/mcp"
}
}
}Any HTTP MCP client — change the server URL from http://localhost:8080/mcp to http://localhost:3000/mcp. The proxy is fully transparent: it forwards all MCP methods unchanged and only intercepts tools/call for policy evaluation.
For requests that pass JSON-RPC ingress validation at /mcp, Helio always returns a JSON-RPC response envelope.
- Valid upstream JSON-RPC responses pass through unchanged at the body layer.
- Upstream forwarding failures and non-JSON-RPC upstream payloads are normalized to:
{"jsonrpc":"2.0","id":<request-id>,"error":{"code":-32603,"message":"...","data":{"failure_class":"..."}}} - HTTP ingress validation errors (for example malformed JSON or wrong
Content-Type) still return transport HTTP errors such as400or415.
Navigate to http://localhost:3100 to open the governance dashboard. If dashboard.api_secret is set (recommended/default), enter that secret on the login screen first.
The dashboard has five tabs:
- Feed — Real-time stream of tool calls and policy decisions
- Approvals — Pending approval queue (for
require_approvalrules) - Audit — Searchable log of every recorded action with filters
- Limits — Current rate and spend limit status per tool
- Analytics — Charts showing action volume, decision breakdown, and top tools
With the proxy running, send a request through it:
If
jqis not installed, remove the trailing| jqfrom the commands below.
# List available tools
curl -s -X POST http://localhost:3000/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq# Call a tool
curl -s -X POST http://localhost:3000/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_weather","arguments":{"city":"London"}}}' | jqThe tool call passes through the policy engine, gets forwarded to the upstream server, and the response is returned — with an audit record created in the background.
Add a rule to your config and watch hot-reload pick it up. With the proxy still running, edit helio.yaml and add a deny rule before the existing rules:
rules:
- name: block-email
match:
tool: 'send_email'
action: deny
feedback:
message: 'Email sending is disabled.'
suggestion: 'Contact your admin to enable email.'
- name: block-destructive
# ... existing rulesSave the file. The proxy detects the change and reloads:
[helio] Policy reloaded: 3 rules (default: allow)
Now try calling the blocked tool:
curl -s -X POST http://localhost:3000/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"send_email","arguments":{"to":"alice@example.com","body":"Hello"}}}' | jqThe response includes a structured error with the feedback message and suggestion — information an AI agent can use to self-correct.
You can also run Helio with Docker Compose. One-time setup:
cd docker
cp .env.example .env
openssl rand -hex 32 # paste this into .env as HELIO_DASHBOARD_SECRET
docker compose upThis starts the proxy, a demo MCP echo server, and the dashboard. By default both published ports are bound to 127.0.0.1 on the host (not the LAN) so the quickstart is safe on untrusted networks. See docker/README.md for the security model and how to opt in to LAN or remote access.
To run Helio in its own container next to a coding agent or dev container — so the agent is forced through governance and can't reach the MCP server directly — see Running Helio as a Sidecar.
The quickstart above is safe by default on a single-operator workstation. Before running Helio anywhere reachable from other people or machines, work through this checklist:
- Generate and store a strong
dashboard.api_secret.helio initwrites a fresh 256-bit hex value intohelio.yaml; if you authored the file by hand, runopenssl rand -hex 32and paste it intodashboard.api_secret. This value is stable until you rotate it. Treat it like a password-manager secret: if you lose it, you must generate a new one, updatehelio.yaml, and restart/reload the proxy. - Understand browser login behavior. The dashboard UI now uses a manual secret login screen: enter
dashboard.api_secretonce, then the browser uses a short-lived HttpOnly session cookie. The raw secret is not injected into frontend JS anymore. - Keep
dashboard.hoston127.0.0.1. The dashboard sideband assumes a single trust boundary and has no identity layer of its own. If you need to reach it remotely, put it behind a reverse proxy (nginx, Caddy, Cloudflare Access, Tailscale Serve) that terminates TLS and adds per-user authentication before forwarding to127.0.0.1:3100. Do not binddashboard.hostto0.0.0.0directly. - Keep the audit database on local disk. Helio's audit sqlite file is created with mode
0600(owner read/write only) — if you move it to a shared filesystem or back it up into an unencrypted bucket, you lose that isolation. Audit records contain tool inputs and upstream responses, often including PII and credentials. - Rotate secrets deliberately and expect session invalidation. The proxy reads
dashboard.api_secretfrom disk on startup and on config hot-reload. Change the value inhelio.yaml, then restart/reload to rotate. Existing dashboard sessions are revoked and users must log in again with the new secret. - Keep the main MCP port behind the same trust boundary as the dashboard.
listen.hostdefaults to127.0.0.1; if you need remote agents, use a reverse proxy or SSH tunnel rather than binding to0.0.0.0. The main port does not carry the dashboard secret — it accepts MCP traffic based on network reachability alone.
- Configuration Reference — Every
helio.yamlfield with defaults and types - Running Helio as a Sidecar — Deploy next to a coding agent / dev container so it can't bypass governance
- Policy Guide — Rule syntax, matchers, actions, rate limits, spend limits, and common patterns
- Approval Workflows — Route sensitive actions to humans via Slack, webhook, or dashboard
- Audit Trail — What's recorded, how to search, and how to export
- Examples — Three runnable configurations: basic, slack-approvals, spend-limits

