0.13.0 Build on MDK
Seoul Hackathon Nov 2026
What is MDK?
MDK, the Mining Development Kit, is an open source framework that lets your code read and control any kind of device or service. The same building blocks serve a home miner on a shelf and a site with thousands of machines.
Every miner turns electricity into hashrate and heat. At home, that makes it a heater that earns bitcoin; at scale, it makes a site one of the most flexible loads on the grid. MDK is the data and control layer that lets software and AI agents see those machines and steer them.
You build on two pieces:
- Workers connect a device or service to MDK: a miner's firmware, a power meter, a cooling system, or an API such as a power-price feed. A Worker is a declarative contract filled in from the device's spec, designed so a coding agent can write one.
- Apps are standalone projects on top. They read live data and send commands through MDK, and several apps can run against the same site.
Where your code plugs in · 4 layers
Data flows up through MDK to your app, and commands flow back down. Write a Worker when MDK can't reach a device yet, and an app when it already can.
You don't have to start from zero: there's already a set of Workers built by the industry that you can use. Every Worker comes with mock data, so you can test against it without hardware.
The MDK CLI is the entry point for both. MDK is open source under Apache 2.0 and vendor-neutral, and it runs on your own infrastructure.
Mining comes first, but the goal is bigger: a standard for how intelligent systems work with the physical world, from mining sites to data centers and robotics. What you build at this hackathon is an early piece of it.
Challenges and prizes
MDK has two challenges at this hackathon, each with one winner who takes home a Heatbit Bitair, an air purifier with a built-in solo Bitcoin miner, courtesy of Heatbit.
For both, you can build an MDK app, a new Worker for a device or service MDK doesn't support yet, or both. No hardware is needed: you can build against MDK's mock environment. The ideas below are starting points, not a checklist.
Hashrate at home
A home miner is a heater that earns bitcoin. This prize goes to the best project built on MDK that makes mining useful at home: it heats what needs heating, reacts to what's happening in the house, and runs without supervision.
Ideas
- Heat a pool, hot tub, water tank or greenhouse, mining only as hard as the heat is needed
- A comfort controller that runs miners based on room temperature, air quality, and quiet hours
- Mine on solar surplus or off-peak power, and back off when electricity gets expensive
- Show the real cost of heat: sats earned versus a regular electric heater
What judges look for
- Real problem: would someone mining at home install it and keep using it?
- It works: a live demo on real hardware or the mock environment beats slides
- Built on MDK: telemetry and control go through MDK, not around it
- Everyday UX: a non-technical household can set it up and understand it
- Safe by default: respects device limits and fails safe if a sensor or the network drops
- Bonus: open source and documented, so others can build on it
Hashrate at scale
A mining site is one of the most flexible large loads on the grid: it can shed power within seconds and bring it back on demand. This prize goes to the best project built on MDK that turns that flexibility, and the data behind it, into value for operators, energy producers, or the grid. Workers can wrap APIs too, such as a power-price feed or a grid signal.
Ideas
- Curtailment: throttle or power down automatically on price spikes, grid alerts or a site power cap, then ramp back safely
- Demand response: answer grid signals fast enough to sell flexibility, and prove the megawatts delivered
- Profit-aware control: set each machine's power target from hashprice, energy cost, and its own efficiency
- Follow the output of a solar or wind farm, or a flare-gas site
- Site health: catch failing machines, hot spots or cooling problems early, or build an AI agent that proposes fixes for an operator to approve
What judges look for
- Realism: would an operator trust it with a real fleet, and does it match how sites and energy markets work?
- Safety: gradual ramps, hardware and contract limits respected, sensible behavior when data or connectivity is missing, a manual override
- Measurable impact: show the numbers (cost saved, MW curtailed, response time, revenue) on real or simulated data
- Built on MDK: telemetry and control go through MDK, not around it
- Scale and demo: works from one rack to thousands of machines, and the demo actually runs
How to
Prerequisites
Both paths below need:
Cloning the example site, or any site run under PM2 rather than mdk run, also needs:
- PM2 (
npm install -g pm2)
If npm install -g fails with EACCES, your global npm folder belongs to root, as is common on Linux and in containers.
Install Node.js with a version manager such as nvm, or run the install with sudo.
- Outbound UDP to the HyperDHT bootstrap nodes. Conference Wi-Fi may block, Set a private DHT if it does.
Run a mining site end to end and Run the CLI with npm carry the maintained lists.
MDK supports 3 ways of building and you can mix them:
- Run a site: put existing workers and apps together into a running site.
- Write a worker or an app for your own site.
- Publish a worker or an app for any site to install.
For both challenges, the usual entry is: start from a site that runs on mocks, then add a worker for the signal you need (a temperature, a price, a grid event). Finally, write an app that reads the data and sets miner power.
The example site, https://github.com/tetherto/mdk-app-example-site, does all of this. It runs three miner brands, four pools, a power meter, and five apps against mock devices. See its README, section "Dependencies and how they are integrated".
MDK npm packages
(all 0.13.x, scope @tetherto)
The example site pins every package to 0.13.0. npm i -g @tetherto/mdk-cli@0.13.0 pins only the CLI: the packages it runs and
scaffolds resolve to the newest 0.13.x patch.
| Package | What it gives you |
|---|---|
mdk-cli | The mdk command, which runs and checks a stack: onboard (writes mdk.yaml), create worker, create app, create dashboard, add worker and add app (from npm or GitHub), validate, run, status, skill add |
mdk-skill | Agent skills (SKILL.md) for coding agents such as Claude Code or Cursor. Install them with mdk skill add |
mdk-kernel | The Kernel: device and worker registry, access policy, command routing |
mdk-core | Bootstrap helpers for the Kernel and Gateway, local discovery, and services for workers (logs, alerts, stats, settings) |
mdk-worker | The worker runtime (WorkerRuntimeV2). It hosts a worker plugin's devices and connects them to the Kernel |
mdk-gateway | The HTTP gateway. It serves app routes (/apps/<name>/…) and the UI |
mdk-app-runtime | Runs one app's controllers in its own process |
mdk-client | Client that calls the Kernel over HRPC |
mdk-identity, mdk-rbac | Users, service accounts and tokens, and role-based access checks at the Gateway, MCP server and Kernel |
mdk-mcp | MCP server that exposes app routes as tools for AI agents |
mdk-ui-foundation | Headless UI state (Zustand stores, TanStack Query client) |
mdk-react-adapter | React bindings, and the host side of app UI modules (MdkAppProvider, checkAppUiModule) |
mdk-react-devkit | React UI primitives and mining components: charts, tables, header stats, sidebar |
mdk-ui-agent | Co-pilot chat panel for the agent app |
To check which workers already exist, see the list of supported workers
The example apps are on GitHub only, not on npm. Install them from GitHub (mdk add app github:tetherto/<repo>, or a git+https://… dependency):
- https://github.com/tetherto/mdk-app-operation-center: site dashboard, explorer, containers, power commands
- https://github.com/tetherto/mdk-app-reporting-tool: hashrate, power, efficiency, cost and pool reports
- https://github.com/tetherto/mdk-app-demo: aggregated live and historical telemetry
- https://github.com/tetherto/mdk-app-agent: AI agent chat sessions with operator approvals
Start here: Hashrate at home
Starting site. A home setup is usually one miner, a meter or smart plug, and one or two sensors. You can get there in two ways:
mdk onboardwith one miner worker on a mock. For example,mdk add worker sealminer/sealminer-mdk-workeradds one SealMiner withconfig.mock: true.mdk runcan only mock a worker that shipsmock/server.js. Among the miner workers that's SealMiner only. The Whatsminer and Antminer mocks run only in the example site, which starts them itself.- Trim the example site. Keep one miner in
config/devices.json, and delete the roles you don't need frompm2.roles(the other miner brands, the pools you don't use,app-demo).
What already exists:
- Miner power control: the commands are listed under "Read data and control miners" below.
- Electricity use: the miner's
powertelemetry, or a site meter (the SATEC power meter worker). - Earnings: the OCEAN pool worker's
estimated_earningsandbalance.
What you'll likely write. Write a worker for each home signal: room or water temperature, air quality, solar output, electricity tariff, quiet hours. Each is usually small. Start from one of these:
- A sensor or plug on your LAN: copy a miner worker's structure, and keep only its
telemetryand the one or two commands you need. - A web API, such as a tariff or a solar forecast: use
ocean-mdk-workeras the model. It wraps a pool's HTTP API and reportstelemetryonly, with no commands. Give it amock/that replays a realistic day: a cold morning, a price spike, a sunny afternoon.
The app. A control loop that runs on its own:
- Read the room temperature (or tariff, or solar surplus).
- Pick a power mode or power percentage for the miner.
- Send it.
- Check the result on the next telemetry poll.
Run the loop from the app's main (see mdk-app-operation-center/plugin/main.js for a background sampler). Give it a small UI page so a household can set a target temperature and quiet hours, and see what the miner is doing and why.
The "real cost of heat" comparison. Mining heat versus an electric heater is a calculation over data MDK already has:
- Electricity used: energy in kWh from the miner's
power(or the meter) × your tariff. - Earnings: sats from the pool worker's
estimated_earnings, or from the hashrate and network difficulty. - The comparison: an electric heater turns the same kWh into the same heat, with no sats. The
/api/reporting/financialsand/api/reporting/costsroutes in the Reporting Tool show one way to structure these numbers.
Start here: Hashrate at scale
Starting site. Use the example site as it is. It has 10 miners in 3 containers, a site power meter, pools, the Operation Center and the Reporting Tool. To show scale, add more mock miners to config/devices.json. Each entry gets its own mock, so the only limit is your machine. Add them to the Operation Center's layout too, so they show up in containers.
What already exists:
- Commands across many miners: the Operation Center sends power commands to many miners at once (
POST /apps/op-center/api/commands/power-modeand/power-pct). Withcommands.verify: truein its config, it waits for each to succeed, so readplugin/lib/commands.jsbefore writing your own. - Measured site power: SATEC site meter readings in the Reporting Tool's
/api/reporting/consumption, and miner power and hashrate atGET /apps/op-center/api/power-meters, with the history at/api/history. The Operation Center lists a meter only once a container'smeterDeviceIdin itslayoutnames it, and the example site's layout names none. Use these to prove the megawatts curtailed and the response time. - Efficiency per miner: the
efficiencyandhashrate_idealtelemetry, plus the Reporting Tool's hardware catalogue (config/reporting/hardware-catalogue.json) with nominal TH/s and watts per model. These let you rank which machines to turn down first. - AI agent with operator approval: the agent app (
@tetherto/mdk-app-agent) runs chat sessions that ask an operator to approve an action before it runs.@tetherto/mdk-ui-agentgives you the chat panel. Themcprole exposes every app's routes as tools. Routes marked"safety": "write"stay hidden unless writes are allowed, so an agent can read freely but can't act on its own.
What you'll likely write:
- A worker for each market signal: energy price (day-ahead or real-time), a grid frequency or demand-response event, a site power cap, or the output of a solar or wind farm. Model it on
ocean-mdk-worker(an API wrapped as telemetry), and give it a mock that can trigger a price spike or a grid event when you ask for one in a demo. - A controller app: it reads those signals and the per-miner efficiency, then sets power targets.
- Give it a dashboard page with the current target, what the site actually draws, and an event log.
- Add a report that adds up MW curtailed, response time, and cost saved or revenue earned.
Read data and control miners
Apps talk to the Kernel through the client the app runtime hands them (require('@tetherto/mdk-gateway/plugin').config.mdkClient). The calls the example apps use are:
| Call | Use |
|---|---|
listWorkers() | Find the workers and their devices |
getCapabilities(deviceId) | Which telemetry and commands a device supports |
pullTelemetry(deviceId, …) | Latest values: hashrate_rt, power, efficiency, temperature, power_mode, status, … |
sendCommand(deviceId, command, params) | Queue a command. It returns { commandId, status: 'QUEUED' } |
getCommandStatus(commandId) | Did it succeed, fail or time out |
Power commands differ by brand. Check getCapabilities before sending one:
| Worker | Power commands |
|---|---|
whatsminer-mdk-worker | setPowerMode { mode: low | normal | high | sleep }, setPowerPct { pct: 0–200 } |
antminer-mdk-worker | setPowerMode { mode: low | normal | high | sleep }, setHashratePercent { pct } |
sealminer-mdk-worker | setPowerMode { mode: low_power | normal | high_performance | sleep }, setPowerLimit { watts: 2000–20000 }, suspend, resume |
sleep and suspend stop the miner. Use them for a full shutdown, and make sure your app can bring miners back. The app needs devices: write in its mdk-app.yaml permissions to send commands, and devices: read plus workers: list to read telemetry.
Safety (both challenges)
Judges weigh safety heavily. Build these in from the start:
- Ramp gradually. Step
setPowerPctorsetPowerLimitover seconds or minutes, and stagger miners. Never switch a whole site at once. - Respect limits. Clamp every target to the ranges the worker's contract declares, the site or contract power cap, and the household's comfort settings. Validate before you send.
- Fail safe. When a sensor, price feed or the network goes silent, fall back to a defined state: hold the current setting, or drop to a safe low mode. Don't keep acting on stale data. Watch telemetry timestamps and the miner
status. - Check that it worked. Follow every command with
getCommandStatusand the next telemetry value. Retry or alert on failure. - Manual override. Give operators a switch that pauses automation, and show when the automation is in control. The Operation Center's
commandsEnabledflag is one example of turning writes off. - Least access. Request only the
permissionsyour app needs. Keep write routes marked"safety": "write"so that MCP agents need explicit permission to use them. - Demo the failures. Kill the price mock or the sensor mock live and show what your app does.
1. Build a site from existing workers and apps
The quickest start is the mdk CLI:
npm i -g @tetherto/mdk-cli@0.13.0mkdir mdk-site-demo && cd mdk-site-demo
mdk onboard
# interactive: writes mdk.yaml and package.json, can add dashboard and MDK skillmdk add worker sealminer/sealminer-mdk-worker
# a miner with a bundled mock (mock: true)mdk add app github:tetherto/mdk-app-operation-centermdk run
# starts the Kernel, workers, apps and Gateway (Ctrl+C stops)# open second terminal
cd mdk-site-demo
mdk status# add new user
cd mdk-site-demo
mdk user add {username} --admin
# prompts for the password twice# to run UI
cd mdk-site-demo
mdk run dashboard
# UI link - http://localhost:3030/
# or: cd apps/dashboard && npm run devNotes from a test run with @tetherto/mdk-cli 0.13.0:
mdk onboardneeds a real terminal. It fails when piped or run from a script.- Ports: the Gateway listens on 3847 and the Kernel on 3848. If another stack already uses those ports,
mdk runstops with "port already in use". Changespec.gateway.portandspec.kernel.portinmdk.yaml. - The example apps aren't on npm, so
mdk add app @tetherto/mdk-app-…fails with a 404. Use thegithub:form above. - Workers without a
mock/server.js(Whatsminer, Antminer) are added without a mock, and their deviceoptsare empty. Fill them in for real hardware. - Sign-in: the Gateway made by
mdk runrequires sign-in, so app routes answer 401 to anonymous requests. Seemdk userandmdk login. - App routes:
mdk add appmounts the Operation Center at/apps/operation-center/…. The/apps/op-center/…paths on this page are the example site's, wheregateway.pluginDirsnames itop-center. - Empty Operation Center: the app's default
layouthas no containers, so its routes answer 200 with no miners, containers or meters, even while the SealMiner is registered. Setlayoutin the app'sconfiginmdk.yaml, shaped like the example in the Operation Center README.
You can also clone mdk-app-example-site and edit it. It wires the same packages by hand under PM2 instead of mdk run:
- Kernel and Gateway:
config/site.deploy.jsonsets the ports and the data directory (.site-data/). It also lists the processes to start underpm2.roles. - Devices:
config/devices.jsonlists the miners and meters. Each worker has a matching mock, so no hardware is needed. - Apps: listed in
gateway.pluginDirs. - Run it:
npm run setup, thennpm start. The site is athttp://localhost:3000.
2. How vendor workers are integrated
Vendor workers are worker plugins published in the vendor's own GitHub repo.
Examples are whatsminer-mdk-worker, antminer-mdk-worker, sealminer-mdk-worker, ocean-mdk-worker, emcd-mdk-worker, spiderpool-mdk-worker and foundry-mdk-worker.
Each one is a package with these parts:
mdk-contract.json: what the device reports and which commands it takes;- handlers that talk to the device;
- a
mock/server that pretends to be the device.
In the example site :
- The worker is a Git dependency in
package.json, pinned to a commit:"whatsminer-mdk-worker": "git+https://github.com/whatsminer/whatsminer-mdk-worker.git#<sha>". - An adapter in
backend/adapters/(for examplewhatsminer-adapter.js) hosts the plugin onWorkerRuntimeV2from@tetherto/mdk-worker. It turns the devices inconfig/devices.jsoninto the runtime's device list. backend/site.jsstarts the worker and registers it with the Kernel. It also starts the vendor's mock on a local port, with one mock device per configured device.deploy/run-process.jsgives each worker its own PM2 roles, such asworker-antminerandmocks-antminer.
With mdk run, steps 2 to 4 aren't needed: mdk add worker <npm name | github:owner/repo> installs the worker and adds it to spec.workers. If the worker ships mock/server.js, mdk add sets config.mock: true and mdk run starts one mock per device. Otherwise you fill in the device opts for real hardware.
3. How workers from the MDK repo are integrated
Some workers still live inside the MDK monorepo (https://github.com/tetherto/mdk, under backend/workers/…), for example the SATEC PM180 power meter. They aren't published to npm, and they require files elsewhere in the monorepo (backend/core, backend/workers/mock). npm can't install a single subfolder of a Git repo either.
So the example site vendors them instead:
npm run setup:satec(deploy/fetch-satec.js) downloads the MDK release tarball that matches the installed@tetherto/mdk-coreversion.- It extracts the worker and the paths it needs into
vendor/mdk-satec/(gitignored) and runsnpm installthere. backend/site.jsthen loads the worker'sstartSatecWorkerand its mock from that folder and starts them, like the other workers (PM2 rolesworker-satecandmocks-satec).
You can follow the same pattern for any other worker in the MDK repo.
4. How apps are integrated
An app is a package with:
mdk-app.yaml: its id, version, the Kernelpermissionsit needs, an optional backgroundmain, and defaultconfig;plugin/mdk-plugin.json: its HTTP route table, with the controllers that handle each route;- optionally, a UI module in
ui/built withdefineMdkApp(routes, nav sections, panels).
In the example site:
- Install it. The app is a dependency in
package.json, from npm, Git or afile:path. For example:"@tetherto/mdk-app-operation-center": "git+https://github.com/tetherto/mdk-app-operation-center#<ref>". - Configure it. Add a
gateway.pluginDirsentry:{ "name": "op-center", "package": "@tetherto/mdk-app-operation-center", "config": { … } }. Then addapp-op-centertopm2.roles. - Run it. Each app runs in its own
@tetherto/mdk-app-runtimeprocess. It signs in to the Kernel asapp:<name>, which@tetherto/mdk-rbacgrants exactly thepermissionslisted in itsmdk-app.yaml. Its routes are served at/apps/<name>/…. - Show its UI.
ui/src/installedApps.tsximports the app'sui/index.jsandui/style.css, then validates the module withcheckAppUiModule. It mounts the app's pages at#/app/<name>/…insideMdkAppProvider, which sends the app's API calls to/apps/<name>. The app's routes also become a group in the left nav.ui/vite.config.tsdedupes React and the@tethertoUI packages, so the apps share the host's copies.
With mdk run, steps 2 and 3 are done by mdk add app <npm package | github:owner/repo>. It adds the app under spec.apps and writes its service account and its role in spec.rbac. mdk create dashboard scaffolds a UI shell that loads app UIs.
5. Add your own worker or app to a site
A worker is for a device or API that MDK can't reach yet: a heater, a smart plug, a thermostat, a power-price feed, a grid signal.
- Scaffold it with
mdk create worker <name>in the project. It writesworkers/<name>/withmdk-contract.json,src/,mock/server.jsand a smoke test, and registers the worker inmdk.yamlwithmock: true. Without anmdk.yamlit still scaffolds the worker and prints the entry to add. Or ask a coding agent with the MDK skill installed (mdk skill add --client claude|cursor|all). - Fill in
mdk-contract.jsonfrom the device's spec, write the handlers, and add amock/server.js. The mock is what you'll demo with, so make it easy to trigger the interesting cases. - Add it to the site:
mdk runsites: list it by local path underspec.workers(package: ./workers/<name>).- The example site: add it as a
file:dependency, then add an adapter (copybackend/adapters/sealminer-adapter.js), aboot…Workerfunction inbackend/site.js, the mock and worker roles indeploy/run-process.js, and its devices inconfig/devices.json.
An app reads telemetry and sends commands: a curtailment controller, a heat scheduler, an efficiency report.
- Scaffold it with
mdk create app <name>. It writesapps/<name>/withmdk-app.yaml,plugin/(route table and controllers), and a UI inui-src/, built toui/withnpm run build:ui. It also registers the app and its access role inmdk.yaml. - Declare its routes in
plugin/mdk-plugin.json, marking each one"safety": "read-only"or"write". Declare its Kernelpermissionsinmdk-app.yaml, requesting only the access it needs. After you change them, runmdk add app <name> --accept-permissionsto update its role inmdk.yaml, ormdk validate appfails. - Put any control loop in the app's
main. - Optionally, add a
ui/module made withdefineMdkAppand@tetherto/mdk-react-devkit. - Add it to the site:
mdk runsites:mdk create appalready lists it underspec.apps.- The example site: add a
gateway.pluginDirsentry with"dir": "../my-app"(or afile:dependency and"package"), addapp-<name>topm2.roles, and add its UI toINSTALLED_APPSinui/src/installedApps.tsxif it has one.
- Check it with
mdk validate app <name>.
6. Publish a worker or app for others
A worker or app doesn't have to live inside a site. Build it as its own repo, so any MDK site can install it. This also earns the judges' "open source and documented" bonus:
- Package name: name it like the existing ones (
<vendor>-mdk-worker,mdk-app-<name>). - Package contents: a worker ships
mdk-contract.json, its handlers andmock/. An app shipsmdk-app.yaml,plugin/and the builtui/. Use thefilesfield inpackage.jsonto choose what gets published. - Dependencies: list
@tetherto/mdk-*aspeerDependencies(or with a compatible range), so the site's copies are used. - Development setup: keep a small
mdk.yamldev stack in the repo (for exampledev/) that runs your package against mocks withmdk run. Runmdk validate app|worker --path . --smoke --strictin CI. - Publishing: publish to npm, or tag a GitHub release. Sites then install it with
mdk add worker|app <package>orgithub:owner/repo#tag, or as a Git dependency as the example site does. - Docs: document the config keys, the permissions it asks for and its safety behavior in the README. Add a mock so others can try it without hardware.
- Listing: open a PR to add a worker to the supported-workers list.
Before you demo
- It runs on mocks from a clean clone with one command, and the README says which one.
- It shows numbers: for home, comfort held, and sats versus heater cost; for scale, MW curtailed, response time, and cost saved or revenue earned.
- It shows a failure: a sensor, feed or the network drops, and the system ends up in a safe state.
- It goes through MDK: telemetry comes from
pullTelemetryand control fromsendCommand. It never talks to a miner's own API directly.