MDK Logo

Run a mining site end to end

[⏱️ ~3 min] From git clone to a live site with multiple Workers, mock hardware, and a browser dashboard

If Kernel, Gateway, Worker, manager, or thing are unfamiliar, read terminology first.

Overview

This tutorial runs the Starter site example end to end: a Whatsminer worker, an Ocean pool worker, and a SATEC powermeter worker, each backed by mock hardware that speaks the real wire protocol, a Gateway HTTP API, and a React dashboard, all supervised by PM2.

What you'll have at the end:

  • Mock miners, a mock pool, and a mock powermeter, each driven by the real Worker driver code against a localhost mock instead of hardware
  • A Gateway API on :3000 serving /site/overview, /site/history, /site/miners/:id/command, and /site/miners/:id/pools
  • A React dashboard on :3000 with Dashboard, Containers, Monitoring, Pools, and Control pages
  • Two MCP surfaces exposing the site as tools for AI agents: the Gateway's auto-exported routes on :3100, and a hand-authored, agent-contract tool set on :3101

Every component above runs as its own PM2-supervised OS process, discovering the Kernel over a shared local directory rather than a DHT.

Prerequisites

  • Node.js >=24 (LTS)
  • npm >=11
  • PM2 (npm install -g pm2)

Install the example

1.1 Clone the repo

git clone git@github.com:tetherto/mdk.git
cd mdk

1.2 Run setup

cd examples/mvp-site
npm run setup
npm run setup:config

setup installs backend/core, backend/workers, the UI workspace devkit packages, and this example's own dependencies. setup:config copies the committed *.json.example config files into place without overwriting any that already exist.

The script walks several workspaces; first run takes 1-2 minutes.

Start the site

npm start

start.js generates deploy/ecosystem.config.js from config/site.deploy.json, starts the PM2 apps, then exits — PM2 itself keeps the processes running in the background.

pm2 list      # mocks, mocks-ocean, mocks-satec, kernel, worker, worker-ocean, worker-satec, gateway, mcp

Wait for every app to show online, then open http://localhost:3000/ in a browser. The dashboard shows live hashrate, power, and per-device status.

PM2 showing every app online means the processes started, not that the site has fully settled. The Ocean pool worker ticks its mock fetch/save cycle every 10 seconds, and Kernel's local-discovery scan runs every 4 seconds, so /site/overview can report "pools": 0 for 15-20 seconds after pm2 list goes green. Retry the command below if the first response comes back short.

Verify via the API:

curl -s http://localhost:3000/site/overview | jq '{miners: (.miners|length), pools: (.pools|length), powermeters: (.powermeters|length)}'

Expected output:

{
  "miners": 5,
  "pools": 1,
  "powermeters": 1
}

config/devices.json seeds five miners and one powermeter by default. Add or remove entries there to resize the fleet; see configure devices for the format.

(Optional) Explore the UI pages

The Dashboard, Containers, Monitoring, Pools, and Control pages are all served from ui/dist via the Gateway on the same :3000 port. The Pools page reads the Ocean pool worker's stats; the Monitoring page charts the SATEC meter's power series over time. See UI pages for what each one shows.

(Optional) Connect an AI agent over MCP

There are a range of agents and connection modes; apply the method for your agent. For Claude CLI: cd examples/mvp-site claude Accept Use this MCP server Then your agent can query and act on the site's devices.

The example exposes MCP two ways, both listed in .mcp.json.example:

  • The Gateway auto-exports its own /site/* routes as tools, on :3100
  • A hand-authored tool set with agent-contract metadata — backend/mcp-plugins/site/mcp-plugin.json — on :3101, giving an agent summary-first, closed-vocabulary tools (summarize_site, count_devices, list_devices, get_device, rank_devices, act_device) instead of raw route exports

Point an MCP client at either URL and it can query the fleet or, for the second surface, act on it with operator approval.

Stop the site

npm run stop:pm2

This stops and removes every PM2-managed process for this site: mocks, Workers, Kernel, Gateway, and the MCP servers.

The PM2 daemon itself (pm2 list still shows a God Daemon process) stays running in the background after this — that's expected, since PM2 manages processes across every project on the machine, not just this one. If it ever becomes unresponsive, see stale PM2 processes for pm2 kill, which stops PM2 itself, not just this site.

What just happened

  1. Setup installed backend/core, backend/workers, the MDK UI devkit, and this example, then setup:config seeded its local config from the committed *.example files.
  2. Mock hardware: PM2's mocks, mocks-ocean, and mocks-satec roles each started a mock device server — a Whatsminer miner, an Ocean pool, and a SATEC powermeter — speaking the real wire protocol, so the Worker drivers run their true connect, collect, and command paths against them.
  3. Kernel: the kernel role started the orchestration layer in local-discovery mode, watching a shared directory for Workers to publish their RPC keys to.
  4. Workers: the worker, worker-ocean, and worker-satec roles each dispatched to that family's boot function (startWhatsminerWorker, startOceanPoolWorker, startSatecWorker) to construct a WorkerRuntime, seed its devices from config/devices.json, and publish its RPC key for the Kernel to discover.
  5. Gateway: the gateway role mounted the site plugin declared by backend/gateway-plugins/site/mdk-plugin.json and the built UI from ui/dist, then opened the HTTP server on :3000. The plugin aggregates data across the three Workers through mdkClient.
  6. MCP: the mcp role started both MCP surfaces — the Gateway's auto-exported tools on :3100, and the hand-authored agent-contract tool set on :3101.

Resetting state

State (Kernel key, Worker seeds, device registry) persists in .site-data/. If you change config/devices.json after the first run, reset it before restarting — seed devices are only registered once, on an empty store.

Next steps

On this page