MDK Logo

1. Run the stack

[⏱️ ~3 min] Watch a stack come up — Kernel plus one registered device, no hardware required

Get started · 1 of 3 · Run the stack

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

Overview

This is rung 1 of the Get started ladder: observe. It walks the shortest path from a fresh clone to a running Kernel with one mock Whatsminer registered, no hardware required. Everything runs in one Node process.

What you'll have at the end:

  • A mock Whatsminer M56S serving telemetry on 127.0.0.1:14028
  • An Kernel started and aware of one registered device
  • Device list, telemetry, and available commands printed to the terminal

Prerequisites

  • Node.js >=24 (LTS)
  • npm >=11

HRPC relies on HyperDHT for peer connectivity, including when Kernel and the Worker share a process. Review the network requirements and checks if startup stalls.

Clone and install

1.1 Clone the repo

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

1.2 Install dependencies

The monorepo has two workspaces with their own dependency trees. Install both:

backend/core/install-packages.sh ci
backend/workers/install-packages.sh ci

Each script walks every package.json under its workspace and runs npm ci.

Run the example

node examples/backend/mdk-e2e/run.js

Expected output:

Devices: [ 'WM-001 [miner-wm-m56s]' ]
Telemetry: mining hashrate=295764693.45 power=4000W
Commands: reboot, setPowerMode, setLED, setupPools, setPowerPct, setUpfreqSpeed, registerThing, updateThing, forgetThings, saveSettings, saveComment, editComment, deleteComment

If you see those three lines, the whole stack is up: mock device responding, Worker registered, Kernel started and aware of the device. The script queries the device and then exits cleanly.

If the example fails with EADDRINUSE, a previous run left port 14028 bound. Kill stale Node processes with pkill -f mdk-e2e and retry.

What just happened

Here is what run.js does — the minimum runnable shape for an MDK stack:

const { getKernel, waitForDiscovery } = require('../../../backend/core/mdk')
const { createMdkClient } = require('../../../backend/core/client')
const { startWhatsminerWorker } = require('../../../backend/workers/miners/whatsminer')
const wmMock = require('../../../backend/workers/miners/whatsminer/mock/server')

// Start a mock Whatsminer M56S on port 14028
wmMock.createServer({ port: 14028, host: '127.0.0.1', type: 'm56s', serial: 'WM-001', password: 'admin' })

// Start the Whatsminer Worker with one seeded device
const worker = await startWhatsminerWorker({
  workerId: 'whatsminer-m56s-e2e',
  model: 'm56s',
  storeDir: path.join(ROOT, 'worker-store'),
  kernelTopic: TOPIC,
  seedDevices: [{
    id: 'WM-001',
    info: { serialNum: 'WM-001' },
    opts: { address: '127.0.0.1', port: 14028, password: 'admin' }
  }]
})

// Start Kernel — discovers the Worker via DHT
const kernel = await getKernel({ root: ROOT, topic: TOPIC })
await waitForDiscovery(kernel)

// Connect an MDK client over HRPC
const client = createMdkClient({ hrpc: { key: kernel.getPublicKey() } })
await client.connect()

// Query the device and print results
const list = await client.pullTelemetry(deviceId, 'list')
const tel = await client.pullTelemetry(deviceId, 'metrics')
const caps = await client.getCapabilities(deviceId)

Six steps, in order:

  1. Starts a mock miner. wmMock.createServer({ port: 14028, type: 'm56s', serial: 'WM-001', password: 'admin' }) binds port 14028 with a Whatsminer-compatible API serving canned telemetry.
  2. Starts a Worker. startWhatsminerWorker({ ...seedDevices }) instantiates the Whatsminer manager and seeds it with one device registration at 127.0.0.1:14028. The Worker stores the registration and begins polling the mock.
  3. Starts Kernel. getKernel({ topic }) brings up the kernel and joins the DHT topic. It discovers the Worker and pulls its identity and capabilities.
  4. Waits for discovery. waitForDiscovery(kernel) blocks until the Worker appears in Kernel's registry.
  5. Connects a client. createMdkClient({ hrpc: { key: kernel.getPublicKey() } }) creates an MDK Protocol client that speaks to Kernel over HRPC.
  6. Queries and prints. The script pulls device list, telemetry, and capabilities, prints them, cleans up, and exits.

No Gateway here? Right. Gateway is the translator that lets non-Node consumers — browser UIs, AI agents over MCP — speak MDK Protocol to Kernel. This script holds the Kernel handle in-process and speaks MDK Protocol to it directly. See architecture.md#gateway for when Gateway is mandatory.

Cleanup

The script cleans up automatically on exit. Temporary data lives at os.tmpdir()/e2e-run/ and is removed by the script. If needed, you can manually clean with:

rm -rf "$TMPDIR/e2e-run" /tmp/e2e-run

Continue

Next: 2. Control devices from the CLI — keep a stack running and drive it interactively (Whatsminer plus an MDK REPL).

Next steps

On this page